diff --git a/backend/Controller层重构总结.md b/backend/Controller层重构总结.md deleted file mode 100644 index a1ed9ee..0000000 --- a/backend/Controller层重构总结.md +++ /dev/null @@ -1,148 +0,0 @@ -# Controller层重构总结 - -## 重构概述 - -本次重构主要完成了以下工作: - -1. **创建统一的request和response包结构** -2. **建立全局异常处理机制** -3. **重构所有Controller层代码** -4. **优化接口入参和出参规范** - -## 完成的工作 - -### 1. 创建统一的基础类 - -#### 在emotion-common模块中创建: - -- `BaseRequest` - 基础请求类,包含通用字段如requestId、clientIp、userAgent等 -- `BaseResponse` - 基础响应类,包含通用字段如timestamp、requestId、processingTime等 -- `BasePageRequest` - 基础分页请求类,继承BaseRequest,包含分页参数 -- `BasePageResponse` - 基础分页响应类,继承BaseResponse,包含分页信息 - -#### 异常处理类: - -- `BusinessException` - 业务异常 -- `AuthException` - 认证异常 -- `CaptchaException` - 验证码异常 -- `TokenException` - Token异常 -- `GlobalExceptionHandler` - 全局异常处理器 - -### 2. 各模块request和response类 - -#### emotion-ai模块: -- **Request类**: - - `AiChatRequest` - AI聊天请求 - - `CreateConversationRequest` - 创建会话请求 - - `EmotionAnalysisRequest` - 情绪分析请求 - - `GuestChatRequest` - 访客聊天请求 - - `ConversationListRequest` - 会话列表请求 - -- **Response类**: - - `AiChatResponse` - AI聊天响应 - - `CreateConversationResponse` - 创建会话响应 - - `EmotionAnalysisResponse` - 情绪分析响应 - - `GuestChatResponse` - 访客聊天响应 - - `ConversationListResponse` - 会话列表响应 - -#### emotion-auth模块: -- **Request类**: - - `LoginRequest` - 登录请求 - - `RegisterRequest` - 注册请求 - - `OAuthLoginRequest` - 第三方登录请求 - - `SliderCaptchaVerifyRequest` - 滑块验证码验证请求 - -- **Response类**: - - `LoginResponse` - 登录响应 - - `UserInfoResponse` - 用户信息响应 - - `CaptchaResponse` - 验证码响应 - - `SliderCaptchaResponse` - 滑块验证码响应 - -#### emotion-user模块: -- **Request类**: - - `UserUpdateRequest` - 用户更新请求 - -- **Response类**: - - `UserInfoResponse` - 用户信息响应 - -#### emotion-record模块: -- **Request类**: - - `CreateEmotionRecordRequest` - 创建情绪记录请求 - -- **Response类**: - - `EmotionRecordResponse` - 情绪记录响应 - -### 3. Controller层重构 - -#### 重构原则: -1. **移除业务逻辑** - 所有业务逻辑移至Service层 -2. **统一入参出参** - 使用新的request/response格式 -3. **移除try-catch** - 使用全局异常处理机制 -4. **统一返回格式** - 使用Result包装返回结果 - -#### 已重构的Controller: -- `AiChatController` - AI聊天控制器 -- `GuestChatController` - 访客聊天控制器 -- `AuthController` - 认证控制器 -- `CaptchaController` - 验证码控制器 -- `UserController` - 用户控制器 - -### 4. 全局异常处理 - -#### 异常处理机制: -- 统一异常处理器 `GlobalExceptionHandler` -- 支持多种异常类型处理 -- 自动参数校验异常处理 -- 统一错误响应格式 - -#### 支持的异常类型: -- 业务异常 `BusinessException` -- 认证异常 `AuthException` -- 验证码异常 `CaptchaException` -- Token异常 `TokenException` -- 参数校验异常 `MethodArgumentNotValidException` -- 系统异常 `RuntimeException`、`Exception` - -## 代码规范 - -### 1. 命名规范 -- Request类以`Request`结尾 -- Response类以`Response`结尾 -- 包名使用`request`和`response` - -### 2. 继承关系 -- 所有Request类继承`BaseRequest`或`BasePageRequest` -- 所有Response类继承`BaseResponse`或`BasePageResponse` - -### 3. 注解规范 -- 使用`@Schema`注解描述字段 -- 使用`@Valid`注解进行参数校验 -- 使用`@NotBlank`、`@NotNull`等校验注解 - -### 4. Controller规范 -- 不包含业务逻辑 -- 统一使用Result包装返回结果 -- 不使用try-catch,依赖全局异常处理 -- 接口文档完整 - -## 优势 - -1. **代码结构清晰** - 职责分离明确 -2. **异常处理统一** - 全局异常处理机制 -3. **接口规范统一** - 统一的入参出参格式 -4. **维护性提升** - 代码更易维护和扩展 -5. **开发效率提升** - 减少重复代码 - -## 后续工作 - -1. **Service层接口更新** - 确保Service层使用新的request/response格式 -2. **单元测试编写** - 为重构后的代码编写测试用例 -3. **接口文档更新** - 更新API文档 -4. **性能测试** - 验证重构后的性能表现 - -## 注意事项 - -1. 所有Controller层不再包含业务逻辑 -2. 异常处理统一由GlobalExceptionHandler处理 -3. 新的request/response类需要在Service层中使用 -4. 需要更新相关的单元测试和集成测试 diff --git a/backend/README.md b/backend/README.md deleted file mode 100644 index 4216880..0000000 --- a/backend/README.md +++ /dev/null @@ -1,364 +0,0 @@ -# 情绪博物馆后端微服务 - -基于Spring Cloud Alibaba 2022.0.0.0的微服务架构,为情绪博物馆iOS应用提供后端API服务。 - -## 🏗️ 架构概览 - -### 技术栈 -- **Spring Boot**: 3.0.2 -- **Spring Cloud**: 2022.0.0 -- **Spring Cloud Alibaba**: 2022.0.0.0 -- **JDK**: 17+ -- **MySQL**: 8.0+ -- **Redis**: 7.0+ -- **Nacos**: 2.2.0+ - -### 微服务列表 -| 服务名称 | 端口 | 描述 | 状态 | -|---------|------|------|------| -| emotion-gateway | 8080 | API网关 | ✅ 已实现 | -| emotion-user | 8081 | 用户服务 | ✅ 已实现 | -| emotion-ai | 8082 | AI对话服务 | ✅ 已实现 | -| emotion-record | 8083 | 情绪记录服务 | ✅ 已实现 | -| emotion-growth | 8084 | 成长课题服务 | ✅ 已实现 | -| emotion-explore | 8085 | 地图探索服务 | ✅ 已实现 | -| emotion-reward | 8086 | 成就奖励服务 | ✅ 已实现 | -| emotion-stats | 8087 | 统计分析服务 | ✅ 已实现 | - -## 🚀 快速开始 - -### 环境要求 -- JDK 17+ -- Maven 3.6+ -- MySQL 8.0+ -- Redis 7.0+ -- Nacos 2.2.0+ - -### 1. 环境准备 - -#### 启动Nacos -```bash -# 下载Nacos 2.2.0 -wget https://github.com/alibaba/nacos/releases/download/2.2.0/nacos-server-2.2.0.tar.gz -tar -xzf nacos-server-2.2.0.tar.gz -cd nacos/bin - -# 单机模式启动 -sh startup.sh -m standalone - -# 访问控制台: http://localhost:8848/nacos -# 默认用户名/密码: nacos/nacos -``` - -#### 启动MySQL -```bash -# 创建数据库 -mysql -u root -p -CREATE DATABASE emotion_museum DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - -# 导入数据库结构 -mysql -u root -p emotion_museum < ../mysql_deploy_database.sql -``` - -#### 启动Redis -```bash -redis-server -``` - -### 2. 配置Nacos - -访问 http://localhost:8848/nacos,创建以下配置: - -#### 命名空间 -- 命名空间ID: `emotion-dev` -- 命名空间名: `情绪博物馆开发环境` - -#### 配置文件 - -**common-mysql.yml** -```yaml -spring: - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai - username: root - password: 123456 - hikari: - minimum-idle: 5 - maximum-pool-size: 20 - idle-timeout: 600000 - max-lifetime: 1800000 - connection-timeout: 30000 - -mybatis-plus: - configuration: - map-underscore-to-camel-case: true - log-impl: org.apache.ibatis.logging.stdout.StdOutImpl - global-config: - db-config: - id-type: assign_uuid - logic-delete-field: deleted - logic-delete-value: 1 - logic-not-delete-value: 0 -``` - -**common-redis.yml** -```yaml -spring: - data: - redis: - host: localhost - port: 6379 - password: - database: 0 - timeout: 3000ms - lettuce: - pool: - max-active: 20 - max-idle: 10 - min-idle: 5 - max-wait: 3000ms -``` - -**coze-config.yml** -```yaml -coze: - base-url: https://api.coze.cn - api-key: your-coze-api-key - bot-id: your-bot-id - user-id: emotion-museum-user - timeout: 60 - max-retries: 3 - stream: false - model: - temperature: 0.7 - max-tokens: 1000 - top-p: 0.9 - frequency-penalty: 0.0 - presence-penalty: 0.0 -``` - -### 3. 启动微服务 - -#### 方式一:使用启动脚本(推荐) -```bash -# 启动所有服务 -./start-services.sh - -# 停止所有服务 -./stop-services.sh -``` - -#### 方式二:手动启动 -```bash -# 编译项目 -mvn clean compile -DskipTests - -# 启动网关服务 -cd emotion-gateway -mvn spring-boot:run & - -# 启动用户服务 -cd ../emotion-user -mvn spring-boot:run & -``` - -### 4. 验证服务状态 - -#### 方式一:使用测试脚本(推荐) -```bash -# 运行完整测试 -./test-services.sh -``` - -#### 方式二:手动验证 -```bash -# 健康检查 -curl http://localhost:8080/actuator/health # 网关服务 -curl http://localhost:8081/actuator/health # 用户服务 -curl http://localhost:8082/actuator/health # AI对话服务 -curl http://localhost:8083/actuator/health # 情绪记录服务 -curl http://localhost:8084/actuator/health # 成长课题服务 -curl http://localhost:8085/actuator/health # 地图探索服务 -curl http://localhost:8086/actuator/health # 成就奖励服务 -curl http://localhost:8087/actuator/health # 统计分析服务 - -# Nacos服务列表 -curl http://localhost:8848/nacos/v1/ns/service/list?pageNo=1&pageSize=10 -``` - -## 📡 API文档 - -### 用户服务API - -#### 用户注册 -```bash -curl -X POST http://localhost:8080/api/user/register \ - -H "Content-Type: application/json" \ - -d '{ - "account": "test_user", - "password": "123456", - "username": "测试用户", - "email": "test@example.com", - "phone": "13800138000", - "nickname": "小测试" - }' -``` - -#### 用户登录 -```bash -curl -X POST http://localhost:8080/api/user/login \ - -H "Content-Type: application/json" \ - -d '{ - "account": "test_user", - "password": "123456" - }' -``` - -#### 获取用户信息 -```bash -curl -X GET http://localhost:8080/api/user/info/{userId} \ - -H "Authorization: Bearer {token}" -``` - -### Coze AI服务API - -#### 健康检查 -```bash -curl http://localhost:8080/api/ai/coze/health -``` - -#### 测试AI对话 -```bash -curl -X POST http://localhost:8080/api/ai/coze/test/message \ - -H "Content-Type: application/json" \ - -d "message=你好,我今天感觉有点焦虑&userId=test_user" -``` - -#### 测试情绪分析 -```bash -curl -X POST http://localhost:8080/api/ai/coze/test/emotion \ - -H "Content-Type: application/json" \ - -d "text=我今天心情很好,阳光明媚" -``` - -#### 测试完整对话流程 -```bash -curl -X POST http://localhost:8080/api/ai/coze/test/full-chat \ - -H "Content-Type: application/json" \ - -d "userMessage=我最近工作压力很大,感觉很累&userId=test_user" -``` - -## 🔧 开发指南 - -### 项目结构 -``` -backend/ -├── emotion-common/ # 公共模块 -│ ├── src/main/java/ -│ │ └── com/emotionmuseum/common/ -│ │ ├── entity/ # 基础实体 -│ │ ├── result/ # 统一响应 -│ │ └── util/ # 工具类 -├── emotion-gateway/ # 网关服务 -├── emotion-user/ # 用户服务 -├── emotion-ai/ # AI对话服务(待实现) -├── emotion-record/ # 情绪记录服务(待实现) -├── emotion-growth/ # 成长课题服务(待实现) -├── emotion-explore/ # 地图探索服务(待实现) -├── emotion-reward/ # 成就奖励服务(待实现) -├── emotion-stats/ # 统计分析服务(待实现) -├── start-services.sh # 启动脚本 -├── stop-services.sh # 停止脚本 -└── pom.xml # 父工程POM -``` - -### 添加新微服务 - -1. **创建模块** -```bash -mkdir emotion-new-service -cd emotion-new-service -``` - -2. **创建pom.xml** -```xml - - com.emotionmuseum - backend - 1.0.0 - -emotion-new-service -``` - -3. **添加到父工程** -```xml - - emotion-new-service - -``` - -4. **创建启动类** -```java -@SpringBootApplication(scanBasePackages = {"com.emotionmuseum"}) -@EnableDiscoveryClient -@MapperScan("com.emotionmuseum.newservice.mapper") -public class NewServiceApplication { - public static void main(String[] args) { - SpringApplication.run(NewServiceApplication.class, args); - } -} -``` - -## 🐛 故障排除 - -### 常见问题 - -1. **Nacos连接失败** - - 检查Nacos是否启动:`curl http://localhost:8848/nacos/v1/ns/operator/metrics` - - 检查命名空间是否创建 - - 检查配置文件是否正确 - -2. **数据库连接失败** - - 检查MySQL是否启动:`mysqladmin ping` - - 检查数据库是否创建 - - 检查用户名密码是否正确 - -3. **Redis连接失败** - - 检查Redis是否启动:`redis-cli ping` - - 检查端口是否正确 - -4. **服务启动失败** - - 查看日志文件:`tail -f logs/emotion-*.log` - - 检查端口是否被占用:`lsof -i :8080` - -### 日志查看 -```bash -# 查看所有服务日志 -tail -f logs/*.log - -# 查看特定服务日志 -tail -f logs/emotion-user.log -``` - -## 📊 监控 - -### 健康检查端点 -- 网关: http://localhost:8080/actuator/health -- 用户服务: http://localhost:8081/actuator/health - -### Prometheus指标 -- 网关: http://localhost:8080/actuator/prometheus -- 用户服务: http://localhost:8081/actuator/prometheus - -## 🤝 贡献指南 - -1. Fork项目 -2. 创建功能分支:`git checkout -b feature/new-feature` -3. 提交更改:`git commit -am 'Add new feature'` -4. 推送分支:`git push origin feature/new-feature` -5. 提交Pull Request - -## 📄 许可证 - -本项目采用MIT许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。 diff --git a/backend/admin/api/pom.xml b/backend/admin/api/pom.xml deleted file mode 100644 index 9f5b02a..0000000 --- a/backend/admin/api/pom.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - admin - 1.0.0 - ../pom.xml - - admin-api - admin-api - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - com.emotionmuseum - common - ${project.version} - - - diff --git a/backend/admin/pom.xml b/backend/admin/pom.xml deleted file mode 100644 index 01c2418..0000000 --- a/backend/admin/pom.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - backend - 1.0.0 - ../pom.xml - - admin - pom - - api - server - - diff --git a/backend/admin/server/pom.xml b/backend/admin/server/pom.xml deleted file mode 100644 index 39eb748..0000000 --- a/backend/admin/server/pom.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - admin - 1.0.0 - ../pom.xml - - admin-server - admin - - - com.emotionmuseum - common - ${project.version} - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-actuator - - - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring-boot.version} - - - - diff --git a/backend/ai/Dockerfile b/backend/ai/Dockerfile deleted file mode 100644 index 1dd80b5..0000000 --- a/backend/ai/Dockerfile +++ /dev/null @@ -1,48 +0,0 @@ -# AI服务Dockerfile -FROM openjdk:17-jdk-alpine - -# 设置工作目录 -WORKDIR /app - -# 安装必要的工具 -RUN apk add --no-cache curl tzdata && \ - cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ - echo "Asia/Shanghai" > /etc/timezone - -# 复制Maven构建文件 -COPY pom.xml ./ -COPY emotion-common ./emotion-common -COPY emotion-ai ./emotion-ai - -# 安装Maven -RUN apk add --no-cache maven - -# 构建应用 -RUN mvn clean package -DskipTests -pl emotion-ai -am - -# 创建运行用户 -RUN addgroup -g 1000 emotion && \ - adduser -D -s /bin/sh -u 1000 -G emotion emotion - -# 复制jar文件 -RUN cp emotion-ai/target/emotion-ai-*.jar app.jar - -# 设置文件权限 -RUN chown -R emotion:emotion /app - -# 切换到非root用户 -USER emotion - -# 健康检查 -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ - CMD curl -f http://localhost:19002/actuator/health || exit 1 - -# 暴露端口 -EXPOSE 19002 - -# 启动命令 -ENTRYPOINT ["java", "-jar", \ - "-Xms512m", "-Xmx1024m", \ - "-Djava.security.egd=file:/dev/./urandom", \ - "-Dspring.profiles.active=local", \ - "app.jar"] diff --git a/backend/ai/api/pom.xml b/backend/ai/api/pom.xml deleted file mode 100644 index 585bda1..0000000 --- a/backend/ai/api/pom.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - ai - 1.0.0 - ../pom.xml - - ai-api - ai-api - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - com.emotionmuseum - common - ${project.version} - - - diff --git a/backend/ai/api/src/main/java/com/emotionmuseum/ai/api/client/AiClient.java b/backend/ai/api/src/main/java/com/emotionmuseum/ai/api/client/AiClient.java deleted file mode 100644 index d728c30..0000000 --- a/backend/ai/api/src/main/java/com/emotionmuseum/ai/api/client/AiClient.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.emotionmuseum.ai.api.client; - -import com.emotionmuseum.common.result.Result; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; - -@FeignClient(name = "ai") -public interface AiClient { - - @GetMapping("/api/ai/chat/health") - Result health(); -} - - diff --git a/backend/ai/deploy.sh b/backend/ai/deploy.sh deleted file mode 100644 index 5d8991d..0000000 --- a/backend/ai/deploy.sh +++ /dev/null @@ -1,226 +0,0 @@ -#!/bin/bash - -# emotion-ai 单独部署脚本 -# 作者: emotion-museum -# 日期: 2025-07-18 - -set -e - -# 配置变量 -SERVICE_NAME="emotion-ai" -SERVICE_PORT="19002" -REMOTE_HOST="'root@101.200.208.45'" -REMOTE_BUILD_DIR="/data/builds" -REMOTE_DOCKER_COMPOSE_DIR="/data/docker" -PROFILE="test" -PROJECT_NAME="emotion-museum" - -# 颜色输出 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -# 日志函数 -log_info() { - echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -# 检查远程服务器连接 -check_remote_connection() { - log_info "检查远程服务器连接..." - if ssh -o ConnectTimeout=10 'root@101.200.208.45' "echo 'Connection successful'" > /dev/null 2>&1; then - log_success "远程服务器连接正常" - else - log_error "无法连接到远程服务器 'root@101.200.208.45'" - exit 1 - fi -} - -# 构建服务 -build_service() { - log_info "构建服务: $SERVICE_NAME" - - # 构建父项目依赖 - cd .. - mvn clean install -DskipTests -q - cd emotion-ai - - # 构建当前服务 - if mvn clean package -DskipTests -Ptest -q; then - log_success "服务 $SERVICE_NAME 构建成功" - else - log_error "服务 $SERVICE_NAME 构建失败" - exit 1 - fi -} - -# 创建Dockerfile -create_dockerfile() { - log_info "创建Dockerfile: $SERVICE_NAME" - - ssh 'root@101.200.208.45' "cat > $REMOTE_DOCKER_COMPOSE_DIR/Dockerfile.${SERVICE_NAME} << 'EOF' -# 使用阿里云镜像源的OpenJDK -# 使用Java 17 Alpine镜像 -FROM openjdk:17-alpine - -WORKDIR /app - -# 安装必要的工具 (Alpine Linux使用apk) -RUN apk add --no-cache curl - -COPY ${SERVICE_NAME}-1.0.0.jar app.jar - -RUN mkdir -p /app/logs - -ENV TZ=Asia/Shanghai -RUN ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone - -EXPOSE ${SERVICE_PORT} - -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \\ - CMD curl -f http://localhost:${SERVICE_PORT}/actuator/health || exit 1 - -ENTRYPOINT [\"java\", \"-Djava.security.egd=file:/dev/./urandom\", \"-Xms512m\", \"-Xmx1024m\", \"-jar\", \"app.jar\"] -EOF" -} - -# 部署服务 -deploy_service() { - log_info "开始部署服务: $SERVICE_NAME" - - # 检查jar包 - local jar_file="target/${SERVICE_NAME}-1.0.0.jar" - if [ ! -f "$jar_file" ]; then - log_error "JAR包不存在: $jar_file" - exit 1 - fi - - # 创建远程目录 - ssh 'root@101.200.208.45' " - mkdir -p $REMOTE_BUILD_DIR - mkdir -p $REMOTE_DOCKER_COMPOSE_DIR - mkdir -p /data/logs/emotion-museum - " - - # 删除旧jar包 - log_info "删除远程旧jar包" - ssh 'root@101.200.208.45' "rm -f $REMOTE_BUILD_DIR/${SERVICE_NAME}-*.jar" - - # 上传新jar包 - log_info "上传jar包" - if scp "$jar_file" 'root@101.200.208.45':$REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar; then - log_success "jar包上传成功" - else - log_error "jar包上传失败" - exit 1 - fi - - # 创建Dockerfile - create_dockerfile - - # 停止旧容器 - log_info "停止旧容器" - ssh 'root@101.200.208.45' " - docker stop ${SERVICE_NAME} 2>/dev/null || true - docker rm ${SERVICE_NAME} 2>/dev/null || true - docker rmi ${PROJECT_NAME}/${SERVICE_NAME}:latest 2>/dev/null || true - " - - # 创建Docker网络 - ssh 'root@101.200.208.45' "docker network create emotion-network 2>/dev/null || true" - - # 构建镜像 - log_info "构建Docker镜像" - ssh 'root@101.200.208.45' " - cd $REMOTE_DOCKER_COMPOSE_DIR - # 复制jar包到Docker构建目录 - cp $REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar $REMOTE_DOCKER_COMPOSE_DIR/ - - # 构建镜像 docker build -t ${PROJECT_NAME}/${SERVICE_NAME}:latest -f Dockerfile.${SERVICE_NAME} . - - # 清理临时文件 - rm -f ${SERVICE_NAME}-1.0.0.jar " - - # 启动容器 - log_info "启动新容器" - ssh 'root@101.200.208.45' " - docker run -d \\ - --name ${SERVICE_NAME} \\ - --network emotion-network \\ - -p ${SERVICE_PORT}:${SERVICE_PORT} \\ - -v /data/logs/emotion-museum:/app/logs \\ - -e SPRING_PROFILES_ACTIVE=${PROFILE} \\ - -e MYSQL_HOST=101.200.208.45 \\ - -e MYSQL_PORT=3306 \\ - -e MYSQL_DATABASE=emotion_museum \\ - -e MYSQL_USERNAME=root \\ - -e MYSQL_PASSWORD='EmotionMuseum2025*#' \\ - -e REDIS_HOST=101.200.208.45 \\ - -e REDIS_PORT=6379 \\ - -e REDIS_PASSWORD= \\ - -e REDIS_DATABASE=0 \\ - -e NACOS_SERVER_ADDR=101.200.208.45:8848 \\ - -e NACOS_USERNAME=nacos \\ - -e NACOS_PASSWORD='Peanut2817*#' \\ - --restart unless-stopped \\ - ${PROJECT_NAME}/${SERVICE_NAME}:latest - " - - # 等待启动 - log_info "等待服务启动..." - sleep 15 - - # 检查状态 - if ssh 'root@101.200.208.45' "docker ps | grep ${SERVICE_NAME}" > /dev/null; then - log_success "服务启动成功" - - # 显示日志 - log_info "服务日志 最后20行:" - ssh 'root@101.200.208.45' "docker logs --tail 20 ${SERVICE_NAME}" - - # 健康检查 - log_info "执行健康检查..." - sleep 10 - if ssh 'root@101.200.208.45' "curl -f -s http://localhost:${SERVICE_PORT}/actuator/health" > /dev/null 2>&1; then - log_success "健康检查通过" - else - log_warning "健康检查失败,服务可能仍在启动中" - fi - else - log_error "服务启动失败" - ssh 'root@101.200.208.45' "docker logs ${SERVICE_NAME}" - exit 1 - fi -} - -# 主函数 -main() { - log_info "开始部署 $SERVICE_NAME 服务" - log_info "目标服务器: $REMOTE_HOST" - log_info "服务端口: $SERVICE_PORT" - log_info "部署环境: $PROFILE" - - check_remote_connection - build_service - deploy_service - - log_success "$SERVICE_NAME 服务部署完成!" - log_info "访问地址: http://101.200.208.45:$SERVICE_PORT" -} - -# 执行主函数 -main "$@" diff --git a/backend/ai/pom.xml b/backend/ai/pom.xml deleted file mode 100644 index d80c0a2..0000000 --- a/backend/ai/pom.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - backend - 1.0.0 - ../pom.xml - - ai - pom - - api - server - - \ No newline at end of file diff --git a/backend/ai/server/pom.xml b/backend/ai/server/pom.xml deleted file mode 100644 index 421f09c..0000000 --- a/backend/ai/server/pom.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - 4.0.0 - - - com.emotionmuseum - ai - 1.0.0 - - - ai-server - ai - AI对话服务 - - - - com.emotionmuseum - ai-api - ${project.version} - - - com.emotionmuseum - common - ${project.version} - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - org.springframework.boot - spring-boot-starter-webflux - - - mysql - mysql-connector-java - - - com.alibaba - druid-spring-boot-starter - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-discovery - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-config - - - org.springframework.boot - spring-boot-starter-actuator - - - io.micrometer - micrometer-registry-prometheus - - - \ No newline at end of file diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/AiApplication.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/AiApplication.java deleted file mode 100644 index bd85459..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/AiApplication.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.emotionmuseum.ai; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; -import org.springframework.cloud.openfeign.EnableFeignClients; - -/** - * AI对话服务启动类 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@SpringBootApplication(scanBasePackages = {"com.emotionmuseum"}) -@EnableDiscoveryClient -@EnableFeignClients -@MapperScan("com.emotionmuseum.ai.mapper") -public class AiApplication { - - public static void main(String[] args) { - SpringApplication.run(AiApplication.class, args); - } -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/config/AiConfig.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/config/AiConfig.java deleted file mode 100644 index 760de57..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/config/AiConfig.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.emotionmuseum.ai.config; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.reactive.function.client.WebClient; - -/** - * AI配置类 - * 配置Coze平台HTTP客户端 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Configuration -public class AiConfig { - - @Value("${coze.base-url:https://api.coze.cn}") - private String baseUrl; - - @Value("${coze.token}") - private String token; - - /** - * 配置Coze API客户端 - */ - @Bean - public WebClient cozeWebClient() { - return WebClient.builder() - .baseUrl(baseUrl) - .defaultHeader("Authorization", "Bearer " + token) - .defaultHeader("Content-Type", "application/json") - .build(); - } -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/config/FeatureConfig.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/config/FeatureConfig.java deleted file mode 100644 index 4672187..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/config/FeatureConfig.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.emotionmuseum.ai.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -/** - * 功能开关配置 - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Data -@Component -@ConfigurationProperties(prefix = "features") -public class FeatureConfig { - - /** - * 情绪分析功能配置 - */ - private EmotionAnalysis emotionAnalysis = new EmotionAnalysis(); - - /** - * 聊天功能配置 - */ - private Chat chat = new Chat(); - - @Data - public static class EmotionAnalysis { - /** - * 是否启用情绪分析功能 - */ - private boolean enabled = false; - - /** - * 是否自动进行情绪分析 - */ - private boolean autoAnalyze = false; - } - - @Data - public static class Chat { - /** - * 是否启用聊天功能 - */ - private boolean enabled = true; - - /** - * 是否启用流式聊天 - */ - private boolean stream = false; - } -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/controller/AiChatController.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/controller/AiChatController.java deleted file mode 100644 index 03207a8..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/controller/AiChatController.java +++ /dev/null @@ -1,235 +0,0 @@ -package com.emotionmuseum.ai.controller; - -import com.emotionmuseum.ai.request.*; -import com.emotionmuseum.ai.response.*; -import com.emotionmuseum.ai.service.AiChatService; -import com.emotionmuseum.ai.service.ConversationDbService; -import com.emotionmuseum.ai.entity.Conversation; -import com.emotionmuseum.ai.entity.Message; -import com.emotionmuseum.ai.dto.ChatRequest; -import com.emotionmuseum.ai.dto.ChatResponse; -import com.emotionmuseum.common.response.BasePageResponse; -import com.emotionmuseum.common.dto.PageQuery; -import com.emotionmuseum.common.result.Result; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.validation.Valid; -import java.util.List; - -/** - * AI聊天控制器 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Slf4j -@RestController -@RequestMapping("/api/ai/chat") -@RequiredArgsConstructor -@Validated -@Tag(name = "AI聊天", description = "AI聊天相关接口") -public class AiChatController { - - private final AiChatService aiChatService; - private final ConversationDbService conversationDbService; - - @Operation(summary = "创建会话") - @PostMapping("/conversation/create") - public Result createConversation( - @Valid @RequestBody com.emotionmuseum.ai.dto.CreateConversationRequest request) { - log.info("收到创建会话请求: userId={}, title={}", request.getUserId(), request.getTitle()); - com.emotionmuseum.ai.dto.CreateConversationResponse response = aiChatService.createConversation(request); - return Result.success(response); - } - - @Operation(summary = "发送聊天消息") - @PostMapping("/send") - public Result sendMessage( - @Valid @RequestBody com.emotionmuseum.ai.dto.ChatRequest request) { - log.info("收到聊天请求: userId={}, message={}", request.getUserId(), request.getMessage()); - com.emotionmuseum.ai.dto.ChatResponse response = aiChatService.chat(request); - return Result.success(response); - } - - @Operation(summary = "情绪分析") - @PostMapping("/emotion/analyze") - public Result analyzeEmotion( - @Valid @RequestBody com.emotionmuseum.ai.dto.EmotionAnalysisRequest request) { - log.info("收到情绪分析请求: userId={}, text={}", request.getUserId(), request.getText()); - com.emotionmuseum.ai.dto.EmotionAnalysisResponse response = aiChatService.analyzeEmotion(request); - return Result.success(response); - } - - @Operation(summary = "流式聊天") - @PostMapping("/stream") - public Result streamChat(@Valid @RequestBody com.emotionmuseum.ai.dto.ChatRequest request) { - log.info("收到流式聊天请求: userId={}", request.getUserId()); - String response = aiChatService.streamChat(request); - return Result.success(response); - } - - @Operation(summary = "健康检查") - @GetMapping("/health") - public Result healthCheck() { - log.info("AI服务健康检查"); - boolean isHealthy = aiChatService.healthCheck(); - return Result.success(isHealthy); - } - - @Operation(summary = "获取AI服务信息") - @GetMapping("/info") - public Result getServiceInfo() { - log.info("获取AI服务信息"); - return Result.success("Emotion Museum AI Service - Powered by Spring AI & Coze"); - } - - @Operation(summary = "获取用户会话列表") - @GetMapping("/conversations/{userId}") - public Result> getUserConversations( - @Parameter(description = "用户ID") @PathVariable String userId, - @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNum, - @Parameter(description = "页大小") @RequestParam(defaultValue = "20") Integer pageSize) { - log.info("获取用户会话列表: userId={}, pageNum={}, pageSize={}", userId, pageNum, pageSize); - - PageQuery pageQuery = new PageQuery(); - pageQuery.setPageNum(pageNum); - pageQuery.setPageSize(pageSize); - - List conversations = conversationDbService.getConversationsByUserId(userId, pageQuery); - return Result.success(conversations); - } - - @Operation(summary = "获取会话详情") - @GetMapping("/conversation/{conversationId}") - public Result getConversation(@Parameter(description = "会话ID") @PathVariable String conversationId) { - log.info("获取会话详情: conversationId={}", conversationId); - - Conversation conversation = conversationDbService.getConversationById(conversationId); - return Result.success(conversation); - } - - @Operation(summary = "获取会话消息列表") - @GetMapping("/conversation/{conversationId}/messages") - public Result> getConversationMessages( - @Parameter(description = "会话ID") @PathVariable String conversationId, - @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNum, - @Parameter(description = "页大小") @RequestParam(defaultValue = "50") Integer pageSize) { - log.info("获取会话消息列表: conversationId={}, pageNum={}, pageSize={}", conversationId, pageNum, pageSize); - - PageQuery pageQuery = new PageQuery(); - pageQuery.setPageNum(pageNum); - pageQuery.setPageSize(pageSize); - - List messages = conversationDbService.getMessagesByConversationId(conversationId, pageQuery); - return Result.success(messages); - } - - @Operation(summary = "结束会话") - @PutMapping("/conversation/{conversationId}/end") - public Result endConversation(@Parameter(description = "会话ID") @PathVariable String conversationId) { - log.info("结束会话: conversationId={}", conversationId); - - boolean success = conversationDbService.updateConversationStatus(conversationId, "ended"); - return success ? Result.success() : Result.error("结束会话失败"); - } - - @Operation(summary = "删除会话") - @DeleteMapping("/conversation/{conversationId}") - public Result deleteConversation(@Parameter(description = "会话ID") @PathVariable String conversationId) { - log.info("删除会话: conversationId={}", conversationId); - - boolean success = conversationDbService.deleteConversation(conversationId); - return success ? Result.success() : Result.error("删除会话失败"); - } - - @Operation(summary = "标记消息已读") - @PutMapping("/message/{messageId}/read") - public Result markMessageAsRead(@Parameter(description = "消息ID") @PathVariable String messageId) { - log.info("标记消息已读: messageId={}", messageId); - - boolean success = conversationDbService.markMessageAsRead(messageId); - return success ? Result.success() : Result.error("标记消息已读失败"); - } - - @Operation(summary = "标记会话所有消息已读") - @PutMapping("/conversation/{conversationId}/read") - public Result markConversationMessagesAsRead( - @Parameter(description = "会话ID") @PathVariable String conversationId) { - log.info("标记会话消息已读: conversationId={}", conversationId); - - boolean success = conversationDbService.markConversationMessagesAsRead(conversationId); - return success ? Result.success() : Result.error("标记会话消息已读失败"); - } - - @Operation(summary = "获取拆分后的消息详情") - @GetMapping("/messages/split") - public Result> getSplitMessages( - @Parameter(description = "消息ID列表,逗号分隔") @RequestParam String messageIds) { - log.info("获取拆分消息详情: messageIds={}", messageIds); - - String[] ids = messageIds.split(","); - List messages = conversationDbService.getMessagesByIds(List.of(ids)); - return Result.success(messages); - } - - @Operation(summary = "测试消息拆分功能") - @PostMapping("/test/split") - public Result testMessageSplit(@Valid @RequestBody ChatRequest request) { - log.info("测试消息拆分功能: userId={}, message={}", request.getUserId(), request.getMessage()); - - // 模拟一个包含\n\n的AI回复 - String mockAiReply = "这是第一段回复,介绍了基本功能。我可以帮助你进行日常对话。\n\n" + - "这是第二段回复,详细说明了聊天功能。我能理解你的情感并给出合适的回应。\n\n" + - "这是第三段回复,介绍了情感分析功能。我可以分析你的情绪状态并提供建议。"; - - // 创建或获取会话 - com.emotionmuseum.ai.dto.CreateConversationRequest convRequest = new com.emotionmuseum.ai.dto.CreateConversationRequest(); - convRequest.setUserId(request.getUserId()); - convRequest.setTitle("测试拆分消息"); - com.emotionmuseum.ai.dto.CreateConversationResponse conversation = aiChatService - .createConversation(convRequest); - - // 保存用户消息 - Message userMessage = new Message(); - userMessage.setConversationId(conversation.getConversationId()); - userMessage.setContent(request.getMessage()); - userMessage.setType("text"); - userMessage.setSender("user"); - userMessage.setTimestamp(java.time.LocalDateTime.now()); - userMessage.setStatus("sent"); - userMessage.setIsRead(0); - Message savedUserMessage = conversationDbService.saveMessage(userMessage); - - // 使用拆分逻辑保存AI回复 - List savedAiMessages = aiChatService.saveAiReplyMessages( - conversation.getConversationId(), mockAiReply, null); - - // 构建响应 - ChatResponse response = new ChatResponse(); - Message lastMessage = savedAiMessages.get(savedAiMessages.size() - 1); - response.setMessageId(lastMessage.getId()); - response.setConversationId(conversation.getConversationId()); - response.setContent(mockAiReply); - response.setTimestamp(lastMessage.getTimestamp()); - - // 设置多条消息信息 - if (savedAiMessages.size() > 1) { - response.setMultipleMessages(true); - response.setMessageCount(savedAiMessages.size()); - response.setMessageIds(savedAiMessages.stream() - .map(Message::getId) - .collect(java.util.stream.Collectors.toList())); - } else { - response.setMultipleMessages(false); - response.setMessageCount(1); - } - - return Result.success(response); - } -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/controller/GuestChatController.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/controller/GuestChatController.java deleted file mode 100644 index a50f4d1..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/controller/GuestChatController.java +++ /dev/null @@ -1,182 +0,0 @@ -package com.emotionmuseum.ai.controller; - -import com.emotionmuseum.ai.dto.GuestChatRequest; -import com.emotionmuseum.ai.dto.GuestChatResponse; -import com.emotionmuseum.ai.dto.GuestUserInfo; -import com.emotionmuseum.ai.dto.MessageListResponse; -import com.emotionmuseum.ai.dto.ConversationListResponse; -import com.emotionmuseum.ai.service.GuestChatService; -import com.emotionmuseum.common.interceptor.UserContextInterceptor; -import com.emotionmuseum.common.result.Result; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import jakarta.servlet.http.HttpServletRequest; -import java.time.LocalDateTime; -import java.util.List; - -/** - * 访客聊天控制器 - * 提供访客模式下的聊天功能 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Slf4j -@RestController -@RequestMapping("/ai/guest") -@RequiredArgsConstructor -@Tag(name = "访客聊天", description = "访客模式下的AI聊天功能") -public class GuestChatController { - - private final GuestChatService guestChatService; - private final UserContextInterceptor userContextInterceptor = new UserContextInterceptor(); - - @PostMapping("/chat") - @Operation(summary = "访客聊天", description = "访客模式下发送消息并获取AI回复") - public Result guestChat( - @RequestBody GuestChatRequest request) { - - // 自动获取客户端IP和User-Agent - String clientIp = getClientIp(); - String userAgent = getUserAgent(); - - request.setClientIp(clientIp); - request.setUserAgent(userAgent); - - log.info("访客聊天请求: IP={}, Message={}", clientIp, request.getMessage()); - - return guestChatService.guestChat(request); - } - - @GetMapping("/conversations") - @Operation(summary = "获取访客会话列表", description = "根据IP地址获取访客的历史会话列表") - public Result> getGuestConversations( - @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNum, - @Parameter(description = "页大小") @RequestParam(defaultValue = "20") Integer pageSize) { - - String clientIp = getClientIp(); - log.info("获取访客会话列表: IP={}", clientIp); - - return (Result) guestChatService.getGuestConversations(clientIp, pageNum, pageSize); - } - - @GetMapping("/messages/{conversationId}") - @Operation(summary = "获取会话消息", description = "获取指定会话的所有消息") - public Result> getGuestConversationMessages( - @Parameter(description = "会话ID") @PathVariable String conversationId, - @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNum, - @Parameter(description = "页大小") @RequestParam(defaultValue = "50") Integer pageSize) { - - String clientIp = getClientIp(); - log.info("获取访客会话消息: IP={}, ConversationId={}", clientIp, conversationId); - - return (Result) guestChatService.getGuestConversationMessages(conversationId, clientIp, pageNum, pageSize); - } - - @PostMapping("/end/{conversationId}") - @Operation(summary = "结束会话", description = "结束指定的访客会话") - public Result endGuestConversation( - @Parameter(description = "会话ID") @PathVariable String conversationId) { - - String clientIp = getClientIp(); - log.info("结束访客会话: IP={}, ConversationId={}", clientIp, conversationId); - - return guestChatService.endGuestConversation(conversationId, clientIp); - } - - @GetMapping("/user-info") - @Operation(summary = "获取访客用户信息", description = "根据IP地址获取或创建访客用户信息") - public Result getGuestUserInfo() { - String clientIp = getClientIp(); - String userAgent = getUserAgent(); - - log.info("获取访客用户信息: IP={}", clientIp); - - return guestChatService.getGuestUserInfo(clientIp, userAgent); - } - - @PostMapping("/test-split") - @Operation(summary = "测试拆分功能", description = "测试AI回复的拆分功能") - public Result testSplitFunction( - @RequestBody GuestChatRequest request) { - - log.info("测试拆分功能: Message={}", request.getMessage()); - - // 根据消息内容生成不同的模拟回复 - String mockAiReply; - if (request.getMessage().contains("双换行")) { - mockAiReply = "这是第一段回复,介绍基本功能。\n\n" + - "这是第二段回复,说明聊天功能。\n\n" + - "这是第三段回复,介绍情感分析。\n\n" + - "这是第四段回复,提供使用建议。"; - } else if (request.getMessage().contains("单换行")) { - mockAiReply = "这是第一行回复,介绍基本功能。\n" + - "这是第二行回复,说明聊天功能。\n" + - "这是第三行回复,介绍情感分析。\n" + - "这是第四行回复,提供使用建议。"; - } else { - mockAiReply = "这是一个完整的回复,没有换行符,将作为单条消息处理。包含了所有功能介绍和使用说明。"; - } - - // 创建模拟的访客聊天响应 - GuestChatResponse response = new GuestChatResponse(); - response.setGuestUserId("test_guest_user"); - response.setGuestNickname("测试用户"); - response.setConversationId("test_conversation_" + System.currentTimeMillis()); - response.setUserMessage(request.getMessage()); - response.setAiReply(mockAiReply); - response.setTimestamp(LocalDateTime.now()); - response.setConversationStatus("active"); - response.setIsNewConversation(true); - - log.info("测试拆分功能完成,AI回复长度: {}, 包含\\n\\n: {}, 包含\\n: {}", - mockAiReply.length(), - mockAiReply.contains("\n\n"), - mockAiReply.contains("\n")); - - return Result.success(response); - } - - /** - * 获取客户端真实IP地址 - */ - private String getClientIp() { - try { - ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder - .getRequestAttributes(); - if (attributes == null) { - return "127.0.0.1"; - } - - var request = attributes.getRequest(); - return userContextInterceptor.getClientIpAddress(request); - } catch (Exception e) { - return "127.0.0.1"; - } - } - - /** - * 获取用户代理信息 - */ - private String getUserAgent() { - try { - ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder - .getRequestAttributes(); - if (attributes == null) { - return "Unknown"; - } - - var request = attributes.getRequest(); - return request.getHeader("User-Agent"); - } catch (Exception e) { - return "Unknown"; - } - } -} \ No newline at end of file diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/ChatRequest.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/ChatRequest.java deleted file mode 100644 index 3c7fda8..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/ChatRequest.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.emotionmuseum.ai.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; -import java.util.List; - -/** - * 聊天请求 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Data -@Schema(description = "聊天请求") -public class ChatRequest { - - @Schema(description = "用户ID", example = "user_123") - @NotBlank(message = "用户ID不能为空") - private String userId; - - @Schema(description = "消息内容", example = "我今天感觉有点焦虑,不知道该怎么办") - @NotBlank(message = "消息内容不能为空") - @Size(max = 2000, message = "消息内容不能超过2000字符") - private String message; - - @Schema(description = "对话ID(可选)", example = "conv_123456") - private String conversationId; - - @Schema(description = "消息类型", example = "text") - private String type = "text"; - - @Schema(description = "聊天历史(可选)") - private List history; - - @Schema(description = "是否需要情绪分析", example = "true") - private Boolean needEmotionAnalysis = true; - - @Schema(description = "上下文信息") - private String context; - - /** - * 聊天消息 - */ - @Data - @Schema(description = "聊天消息") - public static class ChatMessage { - @Schema(description = "角色", example = "user") - private String role; // user, assistant - - @Schema(description = "消息内容") - private String content; - - @Schema(description = "时间戳") - private Long timestamp; - } -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/ChatResponse.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/ChatResponse.java deleted file mode 100644 index 738e5d9..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/ChatResponse.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.emotionmuseum.ai.dto; - -import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; - -/** - * 聊天响应 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Data -@Schema(description = "聊天响应") -public class ChatResponse { - - @Schema(description = "消息ID") - private String messageId; - - @Schema(description = "对话ID") - private String conversationId; - - @Schema(description = "AI回复内容") - private String content; - - @Schema(description = "消息类型", example = "text") - private String type = "text"; - - @Schema(description = "发送者", example = "assistant") - private String sender = "assistant"; - - @Schema(description = "响应时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime timestamp; - - @Schema(description = "情绪分析结果") - private EmotionAnalysisResponse emotionAnalysis; - - @Schema(description = "使用情况") - private Usage usage; - - @Schema(description = "元数据") - private Map metadata; - - @Schema(description = "是否为多条消息") - private Boolean multipleMessages = false; - - @Schema(description = "消息数量") - private Integer messageCount = 1; - - @Schema(description = "所有消息ID列表(当拆分为多条消息时)") - private List messageIds; - - /** - * 使用情况 - */ - @Data - @Schema(description = "使用情况") - public static class Usage { - @Schema(description = "输入Token数") - private Integer promptTokens; - - @Schema(description = "输出Token数") - private Integer completionTokens; - - @Schema(description = "总Token数") - private Integer totalTokens; - } -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/ConversationListResponse.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/ConversationListResponse.java deleted file mode 100644 index 57f9f0e..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/ConversationListResponse.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.emotionmuseum.ai.dto; - -import lombok.Data; -import lombok.Builder; -import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; - -import java.time.LocalDateTime; - -/** - * 会话列表响应DTO - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class ConversationListResponse { - - /** - * 会话ID - */ - private String conversationId; - - /** - * 会话标题 - */ - private String title; - - /** - * 会话类型 - */ - private String type; - - /** - * 会话状态 - */ - private String status; - - /** - * 用户ID - */ - private String userId; - - /** - * 用户类型 - */ - private String userType; - - /** - * 消息数量 - */ - private Integer messageCount; - - /** - * 最后活跃时间 - */ - private LocalDateTime lastActiveTime; - - /** - * 创建时间 - */ - private LocalDateTime createTime; - - /** - * 主要情绪 - */ - private String primaryEmotion; - - /** - * 情绪强度 - */ - private Double emotionIntensity; - - /** - * Coze会话ID - */ - private String cozeConversationId; -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/CreateConversationRequest.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/CreateConversationRequest.java deleted file mode 100644 index f68e965..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/CreateConversationRequest.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.emotionmuseum.ai.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotBlank; - -/** - * 创建会话请求 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Data -@Schema(description = "创建会话请求") -public class CreateConversationRequest { - - @Schema(description = "用户ID", example = "user_123") - @NotBlank(message = "用户ID不能为空") - private String userId; - - @Schema(description = "会话标题", example = "今日心情分享") - private String title; - - @Schema(description = "会话类型", example = "emotion_chat") - private String type = "emotion_chat"; - - @Schema(description = "初始消息", example = "你好,我想聊聊今天的心情") - private String initialMessage; - - @Schema(description = "上下文信息") - private String context; -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/CreateConversationResponse.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/CreateConversationResponse.java deleted file mode 100644 index 04464fc..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/CreateConversationResponse.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.emotionmuseum.ai.dto; - -import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; -import java.util.Map; - -/** - * 创建会话响应 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Data -@Schema(description = "创建会话响应") -public class CreateConversationResponse { - - @Schema(description = "会话ID") - private String conversationId; - - @Schema(description = "用户ID") - private String userId; - - @Schema(description = "会话标题") - private String title; - - @Schema(description = "会话类型") - private String type; - - @Schema(description = "会话状态", example = "active") - private String status = "active"; - - @Schema(description = "创建时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; - - @Schema(description = "更新时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime updateTime; - - @Schema(description = "Coze会话ID") - private String cozeConversationId; - - @Schema(description = "元数据") - private Map metadata; -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/EmotionAnalysisRequest.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/EmotionAnalysisRequest.java deleted file mode 100644 index 42b8d72..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/EmotionAnalysisRequest.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.emotionmuseum.ai.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; - -/** - * 情绪分析请求 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Data -@Schema(description = "情绪分析请求") -public class EmotionAnalysisRequest { - - @Schema(description = "用户ID", example = "user_123") - @NotBlank(message = "用户ID不能为空") - private String userId; - - @Schema(description = "待分析文本", example = "我今天感觉很沮丧,工作压力很大") - @NotBlank(message = "待分析文本不能为空") - @Size(max = 1000, message = "待分析文本不能超过1000字符") - private String text; - - @Schema(description = "分析类型", example = "detailed") - private String analysisType = "detailed"; // simple, detailed - - @Schema(description = "语言", example = "zh") - private String language = "zh"; - - @Schema(description = "上下文信息") - private String context; -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/EmotionAnalysisResponse.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/EmotionAnalysisResponse.java deleted file mode 100644 index 64b78b3..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/EmotionAnalysisResponse.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.emotionmuseum.ai.dto; - -import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; - -/** - * 情绪分析响应 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Data -@Schema(description = "情绪分析响应") -public class EmotionAnalysisResponse { - - @Schema(description = "主要情绪", example = "焦虑") - private String primaryEmotion; - - @Schema(description = "情绪强度", example = "0.75") - private Double intensity; - - @Schema(description = "情绪极性", example = "negative") - private String polarity; // positive, negative, neutral - - @Schema(description = "置信度", example = "0.85") - private Double confidence; - - @Schema(description = "情绪分布") - private List emotions; - - @Schema(description = "关键词") - private List keywords; - - @Schema(description = "建议") - private String suggestion; - - @Schema(description = "分析时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime analysisTime; - - @Schema(description = "额外信息") - private Map metadata; - - /** - * 情绪得分 - */ - @Data - @Schema(description = "情绪得分") - public static class EmotionScore { - @Schema(description = "情绪名称") - private String emotion; - - @Schema(description = "得分") - private Double score; - - @Schema(description = "描述") - private String description; - } -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/GuestChatRequest.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/GuestChatRequest.java deleted file mode 100644 index 03f93f7..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/GuestChatRequest.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.emotionmuseum.ai.dto; - -import lombok.Data; - -/** - * 访客聊天请求DTO - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Data -public class GuestChatRequest { - - /** - * 消息内容 - */ - private String message; - - /** - * 会话ID (可选,如果不提供则创建新会话) - */ - private String conversationId; - - /** - * 会话标题 (创建新会话时使用) - */ - private String title; - - /** - * 客户端IP地址 - */ - private String clientIp; - - /** - * 用户代理信息 - */ - private String userAgent; - - /** - * 消息类型 (默认为text) - */ - private String messageType = "text"; - - /** - * 是否流式响应 - */ - private Boolean stream = false; - - /** - * 附加上下文信息 - */ - private String context; -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/GuestChatResponse.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/GuestChatResponse.java deleted file mode 100644 index f092118..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/GuestChatResponse.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.emotionmuseum.ai.dto; - -import lombok.Data; -import lombok.Builder; -import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; - -import java.time.LocalDateTime; - -/** - * 访客聊天响应DTO - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class GuestChatResponse { - - /** - * 访客用户ID - */ - private String guestUserId; - - /** - * 访客昵称 - */ - private String guestNickname; - - /** - * 会话ID - */ - private String conversationId; - - /** - * 会话标题 - */ - private String conversationTitle; - - /** - * 用户消息ID - */ - private String userMessageId; - - /** - * AI回复消息ID - */ - private String aiMessageId; - - /** - * 用户消息内容 - */ - private String userMessage; - - /** - * AI回复内容 - */ - private String aiReply; - - /** - * 消息时间戳 - */ - private LocalDateTime timestamp; - - /** - * 会话状态 - */ - private String conversationStatus; - - /** - * 是否为新会话 - */ - private Boolean isNewConversation; - - /** - * Coze聊天ID - */ - private String cozeChatId; - - /** - * 情绪分析结果 - */ - private EmotionAnalysisResult emotionAnalysis; - - /** - * Token使用情况 - */ - private TokenUsage tokenUsage; - - /** - * 错误信息 (如果有) - */ - private String errorMessage; - - /** - * 情绪分析结果内部类 - */ - @Data - @Builder - @NoArgsConstructor - @AllArgsConstructor - public static class EmotionAnalysisResult { - private String primaryEmotion; - private Double emotionScore; - private Double confidence; - private String emotionTrend; - } - - /** - * Token使用情况内部类 - */ - @Data - @Builder - @NoArgsConstructor - @AllArgsConstructor - public static class TokenUsage { - private Integer promptTokens; - private Integer completionTokens; - private Integer totalTokens; - } -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/GuestUserInfo.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/GuestUserInfo.java deleted file mode 100644 index 141da35..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/GuestUserInfo.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.emotionmuseum.ai.dto; - -import lombok.Data; -import lombok.Builder; -import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; - -import java.time.LocalDateTime; - -/** - * 访客用户信息DTO - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class GuestUserInfo { - - /** - * 访客用户ID (格式: guest_xxx) - */ - private String guestUserId; - - /** - * 客户端IP地址 - */ - private String ipAddress; - - /** - * 用户代理信息 - */ - private String userAgent; - - /** - * 访客昵称 - */ - private String nickname; - - /** - * 访客头像 - */ - private String avatar; - - /** - * 创建时间 - */ - private LocalDateTime createTime; - - /** - * 最后活跃时间 - */ - private LocalDateTime lastActiveTime; - - /** - * 是否为访客用户 - */ - private Boolean isGuest; - - /** - * 会话数量 - */ - private Integer conversationCount; - - /** - * 消息数量 - */ - private Integer messageCount; -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/MessageListResponse.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/MessageListResponse.java deleted file mode 100644 index f3ad021..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/dto/MessageListResponse.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.emotionmuseum.ai.dto; - -import lombok.Data; -import lombok.Builder; -import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; - -import java.math.BigDecimal; -import java.time.LocalDateTime; - -/** - * 消息列表响应DTO - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class MessageListResponse { - - /** - * 消息ID - */ - private String messageId; - - /** - * 会话ID - */ - private String conversationId; - - /** - * 消息内容 - */ - private String content; - - /** - * 消息类型 - */ - private String type; - - /** - * 发送者 - */ - private String sender; - - /** - * 时间戳 - */ - private LocalDateTime timestamp; - - /** - * 消息状态 - */ - private String status; - - /** - * 情绪类型 - */ - private String emotionType; - - /** - * 情绪分数 - */ - private BigDecimal emotionScore; - - /** - * 情绪置信度 - */ - private BigDecimal emotionConfidence; - - /** - * 是否已读 - */ - private Integer isRead; - - /** - * Coze聊天ID - */ - private String cozeChatId; - - /** - * Coze消息ID - */ - private String cozeMessageId; - - /** - * 用户ID - */ - private String userId; - - /** - * 用户类型 - */ - private String userType; -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/entity/Conversation.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/entity/Conversation.java deleted file mode 100644 index 6219124..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/entity/Conversation.java +++ /dev/null @@ -1,185 +0,0 @@ -package com.emotionmuseum.ai.entity; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableLogic; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import com.emotionmuseum.common.entity.BaseEntity; -import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.List; - -/** - * 对话实体 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName(value = "conversation", autoResultMap = true) -public class Conversation extends BaseEntity { - - /** - * 用户ID (注册用户ID或访客用户ID) - */ - @TableField("user_id") - private String userId; - - /** - * 用户类型 (registered: 注册用户, guest: 访客用户) - */ - @TableField("user_type") - private String userType; - - /** - * 对话标题 - */ - @TableField("title") - private String title; - - /** - * 会话类型 - */ - @TableField("type") - private String type; - - /** - * 会话状态 (active, ended, archived) - */ - @TableField("status") - private String status; - - /** - * Coze会话ID - */ - @TableField("coze_conversation_id") - private String cozeConversationId; - - /** - * Bot ID - */ - @TableField("bot_id") - private String botId; - - /** - * Workflow ID - */ - @TableField("workflow_id") - private String workflowId; - - /** - * 初始消息 - */ - @TableField("initial_message") - private String initialMessage; - - /** - * 上下文信息 - */ - @TableField("context") - private String context; - - /** - * 开始时间 - */ - @TableField("start_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime startTime; - - /** - * 结束时间 - */ - @TableField("end_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime endTime; - - /** - * 最后活跃时间 - */ - @TableField("last_active_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime lastActiveTime; - - /** - * 对话摘要 - */ - @TableField("summary") - private String summary; - - /** - * 标签 - */ - @TableField(value = "tags", typeHandler = JacksonTypeHandler.class) - private List tags; - - /** - * 主要情绪 - */ - @TableField("primary_emotion") - private String primaryEmotion; - - /** - * 情绪强度 - */ - @TableField("emotion_intensity") - private BigDecimal emotionIntensity; - - /** - * 情绪趋势 - */ - @TableField("emotion_trend") - private String emotionTrend; - - /** - * 关键词 - */ - @TableField(value = "keywords", typeHandler = JacksonTypeHandler.class) - private List keywords; - - /** - * AI洞察 - */ - @TableField("ai_insights") - private String aiInsights; - - /** - * 分析置信度 - */ - @TableField("confidence") - private BigDecimal confidence; - - /** - * 消息数量 - */ - @TableField("message_count") - private Integer messageCount; - - /** - * 总Token使用量 - */ - @TableField("total_tokens") - private Integer totalTokens; - - /** - * 总费用 - */ - @TableField("total_cost") - private BigDecimal totalCost; - - /** - * 客户端IP地址 (用于访客用户) - */ - @TableField("client_ip") - private String clientIp; - - /** - * 用户代理信息 - */ - @TableField("user_agent") - private String userAgent; -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/entity/CozeApiCall.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/entity/CozeApiCall.java deleted file mode 100644 index 7f13157..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/entity/CozeApiCall.java +++ /dev/null @@ -1,263 +0,0 @@ -package com.emotionmuseum.ai.entity; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import com.emotionmuseum.common.entity.BaseEntity; -import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.Map; - -/** - * Coze API调用记录实体 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName(value = "coze_api_call", autoResultMap = true) -public class CozeApiCall extends BaseEntity { - - /** - * 对话ID - */ - @TableField("conversation_id") - private String conversationId; - - /** - * 消息ID - */ - @TableField("message_id") - private String messageId; - - /** - * Coze聊天ID - */ - @TableField("coze_chat_id") - private String cozeChatId; - - /** - * Coze对话ID - */ - @TableField("coze_conversation_id") - private String cozeConversationId; - - /** - * Bot ID - */ - @TableField("bot_id") - private String botId; - - /** - * Workflow ID - */ - @TableField("workflow_id") - private String workflowId; - - /** - * 用户ID - */ - @TableField("user_id") - private String userId; - - /** - * 请求类型:chat/stream/retrieve/messages - */ - @TableField("request_type") - private String requestType; - - /** - * 用户消息内容 - */ - @TableField("user_message") - private String userMessage; - - /** - * 用户消息类型:text/image/file - */ - @TableField("user_message_type") - private String userMessageType; - - /** - * AI回复内容 - */ - @TableField("ai_reply") - private String aiReply; - - /** - * AI回复类型:text/image/file - */ - @TableField("ai_reply_type") - private String aiReplyType; - - /** - * 请求URL - */ - @TableField("request_url") - private String requestUrl; - - /** - * 请求体 - */ - @TableField(value = "request_body", typeHandler = JacksonTypeHandler.class) - private Map requestBody; - - /** - * 请求头 - */ - @TableField(value = "request_headers", typeHandler = JacksonTypeHandler.class) - private Map requestHeaders; - - /** - * HTTP状态码 - */ - @TableField("response_status") - private Integer responseStatus; - - /** - * 响应体 - */ - @TableField(value = "response_body", typeHandler = JacksonTypeHandler.class) - private Map responseBody; - - /** - * 响应头 - */ - @TableField(value = "response_headers", typeHandler = JacksonTypeHandler.class) - private Map responseHeaders; - - /** - * 轮询次数 - */ - @TableField("poll_count") - private Integer pollCount; - - /** - * 轮询开始时间 - */ - @TableField("poll_start_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime pollStartTime; - - /** - * 轮询结束时间 - */ - @TableField("poll_end_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime pollEndTime; - - /** - * 最终状态:completed/failed/timeout - */ - @TableField("final_status") - private String finalStatus; - - /** - * 调用状态:pending/success/failed/timeout - */ - @TableField("status") - private String status; - - /** - * 开始时间 - */ - @TableField("start_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime startTime; - - /** - * 结束时间 - */ - @TableField("end_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime endTime; - - /** - * 耗时(毫秒) - */ - @TableField("duration_ms") - private Integer durationMs; - - /** - * 输入Token数 - */ - @TableField("prompt_tokens") - private Integer promptTokens; - - /** - * 输出Token数 - */ - @TableField("completion_tokens") - private Integer completionTokens; - - /** - * 总Token数 - */ - @TableField("total_tokens") - private Integer totalTokens; - - /** - * 费用 - */ - @TableField("cost") - private BigDecimal cost; - - /** - * 函数调用记录 - */ - @TableField(value = "function_calls", typeHandler = JacksonTypeHandler.class) - private Map functionCalls; - - /** - * 函数调用结果 - */ - @TableField(value = "function_results", typeHandler = JacksonTypeHandler.class) - private Map functionResults; - - /** - * 错误代码 - */ - @TableField("error_code") - private String errorCode; - - /** - * 错误信息 - */ - @TableField("error_message") - private String errorMessage; - - /** - * 客户端IP - */ - @TableField("client_ip") - private String clientIp; - - /** - * 用户代理 - */ - @TableField("user_agent") - private String userAgent; - - /** - * 会话ID - */ - @TableField("session_id") - private String sessionId; - - /** - * 追踪ID - */ - @TableField("trace_id") - private String traceId; - - /** - * 扩展元数据 - */ - @TableField(value = "metadata", typeHandler = JacksonTypeHandler.class) - private Map metadata; -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/entity/EmotionAnalysis.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/entity/EmotionAnalysis.java deleted file mode 100644 index 4c68289..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/entity/EmotionAnalysis.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.emotionmuseum.ai.entity; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import com.emotionmuseum.common.entity.BaseEntity; -import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; - -/** - * 情绪分析实体 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName(value = "emotion_analysis", autoResultMap = true) -public class EmotionAnalysis extends BaseEntity { - - /** - * 用户ID - */ - @TableField("user_id") - private String userId; - - /** - * 消息ID - */ - @TableField("message_id") - private String messageId; - - /** - * 分析文本 - */ - @TableField("text") - private String text; - - /** - * 主要情绪 - */ - @TableField("primary_emotion") - private String primaryEmotion; - - /** - * 情绪强度 - */ - @TableField("intensity") - private BigDecimal intensity; - - /** - * 情绪极性 - */ - @TableField("polarity") - private String polarity; - - /** - * 分析置信度 - */ - @TableField("confidence") - private BigDecimal confidence; - - /** - * 情绪详情 - */ - @TableField(value = "emotions", typeHandler = JacksonTypeHandler.class) - private List> emotions; - - /** - * 关键词 - */ - @TableField(value = "keywords", typeHandler = JacksonTypeHandler.class) - private List keywords; - - /** - * 建议 - */ - @TableField("suggestion") - private String suggestion; - - /** - * 分析时间 - */ - @TableField("analysis_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime analysisTime; - - /** - * 元数据 - */ - @TableField(value = "metadata", typeHandler = JacksonTypeHandler.class) - private Map metadata; -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/entity/GuestUser.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/entity/GuestUser.java deleted file mode 100644 index 809a271..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/entity/GuestUser.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.emotionmuseum.ai.entity; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import com.emotionmuseum.common.entity.BaseEntity; -import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.time.LocalDateTime; - -/** - * 访客用户实体 - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName("guest_user") -public class GuestUser extends BaseEntity { - - /** - * 访客用户ID (格式: guest_xxx) - */ - @TableField("guest_user_id") - private String guestUserId; - - /** - * 客户端IP地址 - */ - @TableField("ip_address") - private String ipAddress; - - /** - * 用户代理信息 - */ - @TableField("user_agent") - private String userAgent; - - /** - * 访客昵称 - */ - @TableField("nickname") - private String nickname; - - /** - * 访客头像 - */ - @TableField("avatar") - private String avatar; - - /** - * 最后活跃时间 - */ - @TableField("last_active_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime lastActiveTime; - - /** - * 会话数量 - */ - @TableField("conversation_count") - private Integer conversationCount; - - /** - * 消息数量 - */ - @TableField("message_count") - private Integer messageCount; - - /** - * IP地址的地理位置信息 - */ - @TableField("location") - private String location; - - /** - * 设备信息 - */ - @TableField("device_info") - private String deviceInfo; -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/entity/Message.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/entity/Message.java deleted file mode 100644 index 5b68820..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/entity/Message.java +++ /dev/null @@ -1,164 +0,0 @@ -package com.emotionmuseum.ai.entity; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import com.emotionmuseum.common.entity.BaseEntity; -import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.Map; - -/** - * 消息实体 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName(value = "message", autoResultMap = true) -public class Message extends BaseEntity { - - /** - * 对话ID - */ - @TableField("conversation_id") - private String conversationId; - - /** - * 消息内容 - */ - @TableField("content") - private String content; - - /** - * 消息类型:text/voice/image/system - */ - @TableField("type") - private String type; - - /** - * 发送者:user/ai - */ - @TableField("sender") - private String sender; - - /** - * 时间戳 - */ - @TableField("timestamp") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime timestamp; - - /** - * Coze聊天ID - */ - @TableField("coze_chat_id") - private String cozeChatId; - - /** - * Coze消息ID - */ - @TableField("coze_message_id") - private String cozeMessageId; - - /** - * 消息状态:sending/sent/failed/processing - */ - @TableField("status") - private String status; - - /** - * 错误信息 - */ - @TableField("error_message") - private String errorMessage; - - /** - * 情绪分数 - */ - @TableField("emotion_score") - private BigDecimal emotionScore; - - /** - * 情绪类型 - */ - @TableField("emotion_type") - private String emotionType; - - /** - * 情绪分析置信度 - */ - @TableField("emotion_confidence") - private BigDecimal emotionConfidence; - - /** - * 输入Token数 - */ - @TableField("prompt_tokens") - private Integer promptTokens; - - /** - * 输出Token数 - */ - @TableField("completion_tokens") - private Integer completionTokens; - - /** - * 总Token数 - */ - @TableField("total_tokens") - private Integer totalTokens; - - /** - * API调用费用 - */ - @TableField("api_cost") - private BigDecimal apiCost; - - /** - * 是否已读:0/1 - */ - @TableField("is_read") - private Integer isRead; - - /** - * 父消息ID(用于回复链) - */ - @TableField("parent_message_id") - private String parentMessageId; - - /** - * 元数据 - */ - @TableField(value = "metadata", typeHandler = JacksonTypeHandler.class) - private Map metadata; - - /** - * Coze消息角色 (user/assistant/system) - */ - @TableField("coze_role") - private String cozeRole; - - /** - * Coze消息内容类型 (text/image/file等) - */ - @TableField("coze_content_type") - private String cozeContentType; - - /** - * 用户ID (注册用户或访客用户) - */ - @TableField("user_id") - private String userId; - - /** - * 用户类型 (registered/guest) - */ - @TableField("user_type") - private String userType; -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/mapper/ConversationMapper.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/mapper/ConversationMapper.java deleted file mode 100644 index 2d147b2..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/mapper/ConversationMapper.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.emotionmuseum.ai.mapper; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.emotionmuseum.ai.entity.Conversation; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; -import org.apache.ibatis.annotations.Update; - -import java.time.LocalDateTime; -import java.util.List; - -/** - * 对话Mapper - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Mapper -public interface ConversationMapper extends BaseMapper { - - /** - * 根据用户ID查询对话列表 - * - * @param userId 用户ID - * @param limit 限制数量 - * @param offset 偏移量 - * @return 对话列表 - */ - List selectByUserId(@Param("userId") String userId, - @Param("limit") Integer limit, - @Param("offset") Integer offset); - - /** - * 更新对话摘要 - * - * @param conversationId 对话ID - * @param summary 摘要 - * @param aiInsights AI洞察 - * @return 更新行数 - */ - int updateSummary(@Param("conversationId") String conversationId, - @Param("summary") String summary, - @Param("aiInsights") String aiInsights); - - /** - * 更新对话情绪分析 - * - * @param conversationId 对话ID - * @param primaryEmotion 主要情绪 - * @param emotionIntensity 情绪强度 - * @param emotionTrend 情绪趋势 - * @param confidence 置信度 - * @return 更新行数 - */ - int updateEmotionAnalysis(@Param("conversationId") String conversationId, - @Param("primaryEmotion") String primaryEmotion, - @Param("emotionIntensity") Double emotionIntensity, - @Param("emotionTrend") String emotionTrend, - @Param("confidence") Double confidence); - - /** - * 增加消息数量 - * - * @param conversationId 对话ID - * @return 更新行数 - */ - int incrementMessageCount(@Param("conversationId") String conversationId); - - /** - * 根据用户ID查询活跃会话列表 - * - * @param userId 用户ID - * @return 会话列表 - */ - @Select("SELECT * FROM conversation WHERE user_id = #{userId} AND status = 'active' AND is_deleted = 0 ORDER BY update_time DESC") - List selectActiveConversationsByUserId(@Param("userId") String userId); - - /** - * 更新会话最后活跃时间和消息数量 - * - * @param conversationId 会话ID - * @param lastActiveTime 最后活跃时间 - * @param messageCount 消息数量 - * @return 更新行数 - */ - @Update("UPDATE conversation SET last_active_time = #{lastActiveTime}, message_count = #{messageCount}, update_time = NOW() WHERE id = #{conversationId}") - int updateLastActiveTime(@Param("conversationId") String conversationId, - @Param("lastActiveTime") LocalDateTime lastActiveTime, - @Param("messageCount") Integer messageCount); -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/mapper/CozeApiCallMapper.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/mapper/CozeApiCallMapper.java deleted file mode 100644 index ba9cd27..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/mapper/CozeApiCallMapper.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.emotionmuseum.ai.mapper; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.emotionmuseum.ai.entity.CozeApiCall; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Update; - -import java.time.LocalDateTime; - -/** - * Coze API调用记录 Mapper 接口 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Mapper -public interface CozeApiCallMapper extends BaseMapper { - - /** - * 更新API调用状态 - */ - @Update("UPDATE coze_api_call SET status = #{status}, end_time = #{endTime}, update_time = #{updateTime}, response_body = #{responseBody} WHERE id = #{id}") - int updateStatusById(@Param("id") String id, - @Param("status") String status, - @Param("endTime") LocalDateTime endTime, - @Param("updateTime") LocalDateTime updateTime, - @Param("responseBody") String responseBody); - - /** - * 更新API调用状态(带错误信息) - */ - @Update("UPDATE coze_api_call SET status = #{status}, end_time = #{endTime}, update_time = #{updateTime}, error_message = #{errorMessage} WHERE id = #{id}") - int updateStatusWithErrorById(@Param("id") String id, - @Param("status") String status, - @Param("endTime") LocalDateTime endTime, - @Param("updateTime") LocalDateTime updateTime, - @Param("errorMessage") String errorMessage); - -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/mapper/GuestUserMapper.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/mapper/GuestUserMapper.java deleted file mode 100644 index 77df88d..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/mapper/GuestUserMapper.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.emotionmuseum.ai.mapper; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.emotionmuseum.ai.entity.GuestUser; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; -import org.apache.ibatis.annotations.Update; - -/** - * 访客用户Mapper - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Mapper -public interface GuestUserMapper extends BaseMapper { - - /** - * 根据IP地址查找访客用户 - * - * @param ipAddress IP地址 - * @return 访客用户 - */ - @Select("SELECT * FROM guest_user WHERE ip_address = #{ipAddress} AND is_deleted = 0 ORDER BY create_time DESC LIMIT 1") - GuestUser findByIpAddress(@Param("ipAddress") String ipAddress); - - /** - * 根据访客用户ID查找 - * - * @param guestUserId 访客用户ID - * @return 访客用户 - */ - @Select("SELECT * FROM guest_user WHERE guest_user_id = #{guestUserId} AND is_deleted = 0") - GuestUser findByGuestUserId(@Param("guestUserId") String guestUserId); - - /** - * 更新最后活跃时间 - * - * @param guestUserId 访客用户ID - * @return 更新行数 - */ - @Update("UPDATE guest_user SET last_active_time = NOW(), update_time = NOW() WHERE guest_user_id = #{guestUserId}") - int updateLastActiveTime(@Param("guestUserId") String guestUserId); - - /** - * 增加会话数量 - * - * @param guestUserId 访客用户ID - * @return 更新行数 - */ - @Update("UPDATE guest_user SET conversation_count = conversation_count + 1, update_time = NOW() WHERE guest_user_id = #{guestUserId}") - int incrementConversationCount(@Param("guestUserId") String guestUserId); - - /** - * 增加消息数量 - * - * @param guestUserId 访客用户ID - * @param count 增加数量 - * @return 更新行数 - */ - @Update("UPDATE guest_user SET message_count = message_count + #{count}, update_time = NOW() WHERE guest_user_id = #{guestUserId}") - int incrementMessageCount(@Param("guestUserId") String guestUserId, @Param("count") int count); -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/mapper/MessageMapper.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/mapper/MessageMapper.java deleted file mode 100644 index c8905eb..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/mapper/MessageMapper.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.emotionmuseum.ai.mapper; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.emotionmuseum.ai.entity.Message; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; -import org.apache.ibatis.annotations.Update; - -import java.util.List; - -/** - * 消息Mapper - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Mapper -public interface MessageMapper extends BaseMapper { - - /** - * 根据对话ID查询消息列表 - * - * @param conversationId 对话ID - * @param limit 限制数量 - * @param offset 偏移量 - * @return 消息列表 - */ - List selectByConversationId(@Param("conversationId") String conversationId, - @Param("limit") Integer limit, - @Param("offset") Integer offset); - - /** - * 标记消息为已读 - * - * @param messageId 消息ID - * @return 更新行数 - */ - int markAsRead(@Param("messageId") String messageId); - - /** - * 批量标记消息为已读 - * - * @param conversationId 对话ID - * @return 更新行数 - */ - int markAllAsRead(@Param("conversationId") String conversationId); - - /** - * 获取对话中的最新消息 - * - * @param conversationId 对话ID - * @param limit 限制数量 - * @return 消息列表 - */ - List selectLatestMessages(@Param("conversationId") String conversationId, - @Param("limit") Integer limit); - - /** - * 根据会话ID查询消息列表(带分页) - * - * @param conversationId 会话ID - * @param limit 限制数量 - * @param offset 偏移量 - * @return 消息列表 - */ - @Select("SELECT * FROM message WHERE conversation_id = #{conversationId} AND is_deleted = 0 ORDER BY timestamp ASC LIMIT #{limit} OFFSET #{offset}") - List selectMessagesByConversationId(@Param("conversationId") String conversationId, - @Param("limit") Integer limit, - @Param("offset") Integer offset); - - /** - * 根据会话ID查询最新消息 - * - * @param conversationId 会话ID - * @param limit 限制数量 - * @return 消息列表 - */ - @Select("SELECT * FROM message WHERE conversation_id = #{conversationId} AND is_deleted = 0 ORDER BY timestamp DESC LIMIT #{limit}") - List selectLatestMessagesByConversationId(@Param("conversationId") String conversationId, @Param("limit") Integer limit); - - /** - * 统计会话消息数量 - * - * @param conversationId 会话ID - * @return 消息数量 - */ - @Select("SELECT COUNT(*) FROM message WHERE conversation_id = #{conversationId} AND is_deleted = 0") - Integer countMessagesByConversationId(@Param("conversationId") String conversationId); - - /** - * 标记消息为已读 - * - * @param messageId 消息ID - * @return 更新行数 - */ - @Update("UPDATE message SET is_read = 1, update_time = NOW() WHERE id = #{messageId}") - int markMessageAsRead(@Param("messageId") String messageId); - - /** - * 批量标记会话消息为已读 - * - * @param conversationId 会话ID - * @return 更新行数 - */ - @Update("UPDATE message SET is_read = 1, update_time = NOW() WHERE conversation_id = #{conversationId} AND is_read = 0") - int markConversationMessagesAsRead(@Param("conversationId") String conversationId); - - /** - * 查询未读消息数量 - * - * @param conversationId 会话ID - * @return 未读消息数量 - */ - @Select("SELECT COUNT(*) FROM message WHERE conversation_id = #{conversationId} AND is_read = 0 AND is_deleted = 0") - Integer countUnreadMessages(@Param("conversationId") String conversationId); -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/request/AiChatRequest.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/request/AiChatRequest.java deleted file mode 100644 index fc510de..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/request/AiChatRequest.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.emotionmuseum.ai.request; - -import com.emotionmuseum.common.request.BaseRequest; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; -import java.util.List; - -/** - * AI聊天请求 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "AI聊天请求") -public class AiChatRequest extends BaseRequest { - - private static final long serialVersionUID = 1L; - - @Schema(description = "用户ID", example = "user_123") - @NotBlank(message = "用户ID不能为空") - private String userId; - - @Schema(description = "消息内容", example = "我今天感觉有点焦虑,不知道该怎么办") - @NotBlank(message = "消息内容不能为空") - @Size(max = 2000, message = "消息内容不能超过2000字符") - private String message; - - @Schema(description = "对话ID(可选)", example = "conv_123456") - private String conversationId; - - @Schema(description = "消息类型", example = "text") - private String type = "text"; - - @Schema(description = "聊天历史(可选)") - private List history; - - @Schema(description = "是否需要情绪分析", example = "true") - private Boolean needEmotionAnalysis = true; - - @Schema(description = "上下文信息") - private String context; - - /** - * 聊天消息 - */ - @Data - @Schema(description = "聊天消息") - public static class ChatMessage { - @Schema(description = "角色", example = "user") - private String role; // user, assistant - - @Schema(description = "消息内容") - private String content; - - @Schema(description = "时间戳") - private Long timestamp; - } -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/request/ConversationListRequest.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/request/ConversationListRequest.java deleted file mode 100644 index d26cdec..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/request/ConversationListRequest.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.emotionmuseum.ai.request; - -import com.emotionmuseum.common.request.BasePageRequest; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - * 会话列表请求 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "会话列表请求") -public class ConversationListRequest extends BasePageRequest { - - private static final long serialVersionUID = 1L; - - @Schema(description = "用户ID", example = "user_123") - private String userId; - - @Schema(description = "会话类型", example = "emotion_chat") - private String type; - - @Schema(description = "会话状态", example = "active") - private String status; - - @Schema(description = "用户类型", example = "guest") - private String userType; - - @Schema(description = "开始时间", example = "2025-01-01 00:00:00") - private String startTime; - - @Schema(description = "结束时间", example = "2025-12-31 23:59:59") - private String endTime; -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/request/CreateConversationRequest.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/request/CreateConversationRequest.java deleted file mode 100644 index f8d243f..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/request/CreateConversationRequest.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.emotionmuseum.ai.request; - -import com.emotionmuseum.common.request.BaseRequest; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import jakarta.validation.constraints.NotBlank; - -/** - * 创建会话请求 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "创建会话请求") -public class CreateConversationRequest extends BaseRequest { - - private static final long serialVersionUID = 1L; - - @Schema(description = "用户ID", example = "user_123") - @NotBlank(message = "用户ID不能为空") - private String userId; - - @Schema(description = "会话标题", example = "今日心情分享") - private String title; - - @Schema(description = "会话类型", example = "emotion_chat") - private String type = "emotion_chat"; - - @Schema(description = "初始消息", example = "你好,我想聊聊今天的心情") - private String initialMessage; - - @Schema(description = "上下文信息") - private String context; -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/request/EmotionAnalysisRequest.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/request/EmotionAnalysisRequest.java deleted file mode 100644 index eade087..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/request/EmotionAnalysisRequest.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.emotionmuseum.ai.request; - -import com.emotionmuseum.common.request.BaseRequest; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; - -/** - * 情绪分析请求 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "情绪分析请求") -public class EmotionAnalysisRequest extends BaseRequest { - - private static final long serialVersionUID = 1L; - - @Schema(description = "用户ID", example = "user_123") - @NotBlank(message = "用户ID不能为空") - private String userId; - - @Schema(description = "待分析文本", example = "我今天感觉很沮丧,工作压力很大") - @NotBlank(message = "待分析文本不能为空") - @Size(max = 1000, message = "待分析文本不能超过1000字符") - private String text; - - @Schema(description = "分析类型", example = "detailed") - private String analysisType = "detailed"; // simple, detailed - - @Schema(description = "语言", example = "zh") - private String language = "zh"; - - @Schema(description = "上下文信息") - private String context; -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/request/GuestChatRequest.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/request/GuestChatRequest.java deleted file mode 100644 index 3b02d03..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/request/GuestChatRequest.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.emotionmuseum.ai.request; - -import com.emotionmuseum.common.request.BaseRequest; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; - -/** - * 访客聊天请求 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "访客聊天请求") -public class GuestChatRequest extends BaseRequest { - - private static final long serialVersionUID = 1L; - - @Schema(description = "消息内容", example = "你好,我想聊聊今天的心情") - @NotBlank(message = "消息内容不能为空") - @Size(max = 2000, message = "消息内容不能超过2000字符") - private String message; - - @Schema(description = "会话ID(可选,如果不提供则创建新会话)", example = "conv_123456") - private String conversationId; - - @Schema(description = "会话标题(创建新会话时使用)", example = "今日心情分享") - private String title; - - @Schema(description = "消息类型", example = "text") - private String messageType = "text"; - - @Schema(description = "是否流式响应", example = "false") - private Boolean stream = false; - - @Schema(description = "附加上下文信息") - private String context; -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/response/AiChatResponse.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/response/AiChatResponse.java deleted file mode 100644 index 451b6dd..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/response/AiChatResponse.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.emotionmuseum.ai.response; - -import com.emotionmuseum.common.response.BaseResponse; -import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; - -/** - * AI聊天响应 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "AI聊天响应") -public class AiChatResponse extends BaseResponse { - - private static final long serialVersionUID = 1L; - - @Schema(description = "消息ID") - private String messageId; - - @Schema(description = "对话ID") - private String conversationId; - - @Schema(description = "AI回复内容") - private String content; - - @Schema(description = "消息类型", example = "text") - private String type = "text"; - - @Schema(description = "发送者", example = "assistant") - private String sender = "assistant"; - - @Schema(description = "响应时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime responseTime; - - @Schema(description = "情绪分析结果") - private EmotionAnalysisResponse emotionAnalysis; - - @Schema(description = "使用情况") - private Usage usage; - - @Schema(description = "元数据") - private Map metadata; - - @Schema(description = "是否为多条消息") - private Boolean multipleMessages = false; - - @Schema(description = "消息数量") - private Integer messageCount = 1; - - @Schema(description = "所有消息ID列表(当拆分为多条消息时)") - private List messageIds; - - /** - * 使用情况 - */ - @Data - @Schema(description = "使用情况") - public static class Usage { - @Schema(description = "输入Token数") - private Integer promptTokens; - - @Schema(description = "输出Token数") - private Integer completionTokens; - - @Schema(description = "总Token数") - private Integer totalTokens; - } - - /** - * 情绪分析响应 - */ - @Data - @Schema(description = "情绪分析响应") - public static class EmotionAnalysisResponse { - @Schema(description = "情绪类型") - private String emotionType; - - @Schema(description = "情绪强度") - private Double intensity; - - @Schema(description = "情绪描述") - private String description; - - @Schema(description = "建议") - private String suggestion; - - @Schema(description = "置信度") - private Double confidence; - } -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/response/ConversationListResponse.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/response/ConversationListResponse.java deleted file mode 100644 index 3222640..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/response/ConversationListResponse.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.emotionmuseum.ai.response; - -import com.emotionmuseum.common.response.BaseResponse; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.Builder; -import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; - -import java.time.LocalDateTime; - -/** - * 会话列表响应 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "会话列表响应") -public class ConversationListResponse extends BaseResponse { - - private static final long serialVersionUID = 1L; - - @Schema(description = "会话ID") - private String conversationId; - - @Schema(description = "会话标题") - private String title; - - @Schema(description = "会话类型") - private String type; - - @Schema(description = "会话状态") - private String status; - - @Schema(description = "用户ID") - private String userId; - - @Schema(description = "用户类型") - private String userType; - - @Schema(description = "消息数量") - private Integer messageCount; - - @Schema(description = "最后活跃时间") - private LocalDateTime lastActiveTime; - - @Schema(description = "创建时间") - private LocalDateTime createTime; - - @Schema(description = "主要情绪") - private String primaryEmotion; - - @Schema(description = "情绪强度") - private Double emotionIntensity; - - @Schema(description = "Coze会话ID") - private String cozeConversationId; -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/response/CreateConversationResponse.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/response/CreateConversationResponse.java deleted file mode 100644 index 42920d0..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/response/CreateConversationResponse.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.emotionmuseum.ai.response; - -import com.emotionmuseum.common.response.BaseResponse; -import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.time.LocalDateTime; -import java.util.Map; - -/** - * 创建会话响应 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "创建会话响应") -public class CreateConversationResponse extends BaseResponse { - - private static final long serialVersionUID = 1L; - - @Schema(description = "会话ID") - private String conversationId; - - @Schema(description = "用户ID") - private String userId; - - @Schema(description = "会话标题") - private String title; - - @Schema(description = "会话类型") - private String type; - - @Schema(description = "会话状态", example = "active") - private String status = "active"; - - @Schema(description = "创建时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; - - @Schema(description = "更新时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime updateTime; - - @Schema(description = "Coze会话ID") - private String cozeConversationId; - - @Schema(description = "元数据") - private Map metadata; -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/response/EmotionAnalysisResponse.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/response/EmotionAnalysisResponse.java deleted file mode 100644 index 705eec2..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/response/EmotionAnalysisResponse.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.emotionmuseum.ai.response; - -import com.emotionmuseum.common.response.BaseResponse; -import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; - -/** - * 情绪分析响应 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "情绪分析响应") -public class EmotionAnalysisResponse extends BaseResponse { - - private static final long serialVersionUID = 1L; - - @Schema(description = "主要情绪", example = "焦虑") - private String primaryEmotion; - - @Schema(description = "情绪强度", example = "0.75") - private Double intensity; - - @Schema(description = "情绪极性", example = "negative") - private String polarity; // positive, negative, neutral - - @Schema(description = "置信度", example = "0.85") - private Double confidence; - - @Schema(description = "情绪分布") - private List emotions; - - @Schema(description = "关键词") - private List keywords; - - @Schema(description = "建议") - private String suggestion; - - @Schema(description = "分析时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime analysisTime; - - @Schema(description = "额外信息") - private Map metadata; - - /** - * 情绪得分 - */ - @Data - @Schema(description = "情绪得分") - public static class EmotionScore { - @Schema(description = "情绪名称") - private String emotion; - - @Schema(description = "得分") - private Double score; - - @Schema(description = "描述") - private String description; - } -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/response/GuestChatResponse.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/response/GuestChatResponse.java deleted file mode 100644 index f4c1b3f..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/response/GuestChatResponse.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.emotionmuseum.ai.response; - -import com.emotionmuseum.common.response.BaseResponse; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.Builder; -import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; - -import java.time.LocalDateTime; - -/** - * 访客聊天响应 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "访客聊天响应") -public class GuestChatResponse extends BaseResponse { - - private static final long serialVersionUID = 1L; - - @Schema(description = "访客用户ID") - private String guestUserId; - - @Schema(description = "访客昵称") - private String guestNickname; - - @Schema(description = "会话ID") - private String conversationId; - - @Schema(description = "会话标题") - private String conversationTitle; - - @Schema(description = "用户消息ID") - private String userMessageId; - - @Schema(description = "AI回复消息ID") - private String aiMessageId; - - @Schema(description = "用户消息内容") - private String userMessage; - - @Schema(description = "AI回复内容") - private String aiReply; - - @Schema(description = "消息时间戳") - private LocalDateTime messageTimestamp; - - @Schema(description = "会话状态") - private String conversationStatus; - - @Schema(description = "是否为新会话") - private Boolean isNewConversation; - - @Schema(description = "Coze聊天ID") - private String cozeChatId; - - @Schema(description = "情绪分析结果") - private EmotionAnalysisResult emotionAnalysis; - - @Schema(description = "Token使用情况") - private TokenUsage tokenUsage; - - @Schema(description = "错误信息(如果有)") - private String errorMessage; - - /** - * 情绪分析结果内部类 - */ - @Data - @Builder - @NoArgsConstructor - @AllArgsConstructor - @Schema(description = "情绪分析结果") - public static class EmotionAnalysisResult { - @Schema(description = "主要情绪") - private String primaryEmotion; - - @Schema(description = "情绪得分") - private Double emotionScore; - - @Schema(description = "置信度") - private Double confidence; - - @Schema(description = "情绪趋势") - private String emotionTrend; - } - - /** - * Token使用情况内部类 - */ - @Data - @Builder - @NoArgsConstructor - @AllArgsConstructor - @Schema(description = "Token使用情况") - public static class TokenUsage { - @Schema(description = "输入Token数") - private Integer promptTokens; - - @Schema(description = "输出Token数") - private Integer completionTokens; - - @Schema(description = "总Token数") - private Integer totalTokens; - } -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/AiChatService.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/AiChatService.java deleted file mode 100644 index bd16d1b..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/AiChatService.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.emotionmuseum.ai.service; - -import com.emotionmuseum.ai.dto.*; -import com.emotionmuseum.ai.entity.Message; - -import java.util.List; - -/** - * AI聊天服务接口 - * - * @author huazhongmin - * @since 2025-07-12 - */ -public interface AiChatService { - - /** - * 创建会话 - * - * @param request 创建会话请求 - * @return 创建会话响应 - */ - CreateConversationResponse createConversation(CreateConversationRequest request); - - /** - * 发送聊天消息 - * - * @param request 聊天请求 - * @return 聊天响应 - */ - ChatResponse chat(ChatRequest request); - - /** - * 情绪分析 - * - * @param request 情绪分析请求 - * @return 情绪分析响应 - */ - EmotionAnalysisResponse analyzeEmotion(EmotionAnalysisRequest request); - - /** - * 流式聊天 - * - * @param request 聊天请求 - * @return 流式响应 - */ - String streamChat(ChatRequest request); - - /** - * 健康检查 - * - * @return 是否健康 - */ - boolean healthCheck(); - - /** - * 保存AI回复消息(支持拆分多条消息) - * - * @param conversationId 会话ID - * @param aiContent AI回复内容 - * @param cozeChatId Coze聊天ID - * @return 保存的消息列表 - */ - List saveAiReplyMessages(String conversationId, String aiContent, String cozeChatId); -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/ConversationDbService.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/ConversationDbService.java deleted file mode 100644 index f7b4c23..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/ConversationDbService.java +++ /dev/null @@ -1,207 +0,0 @@ -package com.emotionmuseum.ai.service; - -import com.emotionmuseum.ai.entity.Conversation; -import com.emotionmuseum.ai.entity.Message; -import com.emotionmuseum.ai.entity.CozeApiCall; -import com.emotionmuseum.common.dto.PageQuery; - -import java.util.List; - -/** - * 会话数据库服务接口 - * - * @author huazhongmin - * @since 2025-07-12 - */ -public interface ConversationDbService { - - /** - * 保存会话 - * - * @param conversation 会话信息 - * @return 保存的会话 - */ - Conversation saveConversation(Conversation conversation); - - /** - * 根据ID查询会话 - * - * @param conversationId 会话ID - * @return 会话信息 - */ - Conversation getConversationById(String conversationId); - - /** - * 根据用户ID查询会话列表 - * - * @param userId 用户ID - * @param pageQuery 分页查询 - * @return 会话列表 - */ - List getConversationsByUserId(String userId, PageQuery pageQuery); - - /** - * 根据用户ID查询活跃会话列表 - * - * @param userId 用户ID - * @return 活跃会话列表 - */ - List getActiveConversationsByUserId(String userId); - - /** - * 更新会话状态 - * - * @param conversationId 会话ID - * @param status 状态 - * @return 是否成功 - */ - boolean updateConversationStatus(String conversationId, String status); - - /** - * 更新会话活跃时间 - * - * @param conversationId 会话ID - * @return 是否成功 - */ - boolean updateConversationActiveTime(String conversationId); - - /** - * 保存消息 - * - * @param message 消息信息 - * @return 保存的消息 - */ - Message saveMessage(Message message); - - /** - * 根据会话ID查询消息列表 - * - * @param conversationId 会话ID - * @param pageQuery 分页查询 - * @return 消息列表 - */ - List getMessagesByConversationId(String conversationId, PageQuery pageQuery); - - /** - * 根据会话ID查询最新消息 - * - * @param conversationId 会话ID - * @param limit 限制数量 - * @return 消息列表 - */ - List getLatestMessages(String conversationId, Integer limit); - - /** - * 标记消息为已读 - * - * @param messageId 消息ID - * @return 是否成功 - */ - boolean markMessageAsRead(String messageId); - - /** - * 标记会话所有消息为已读 - * - * @param conversationId 会话ID - * @return 是否成功 - */ - boolean markConversationMessagesAsRead(String conversationId); - - /** - * 统计会话消息数量 - * - * @param conversationId 会话ID - * @return 消息数量 - */ - Integer getMessageCount(String conversationId); - - /** - * 统计未读消息数量 - * - * @param conversationId 会话ID - * @return 未读消息数量 - */ - Integer getUnreadMessageCount(String conversationId); - - /** - * 删除会话 - * - * @param conversationId 会话ID - * @return 是否成功 - */ - boolean deleteConversation(String conversationId); - - /** - * 根据Coze对话ID查询会话 - * - * @param cozeConversationId Coze对话ID - * @return 会话信息 - */ - Conversation getConversationByCozeId(String cozeConversationId); - - /** - * 更新会话的Coze相关信息 - * - * @param conversationId 会话ID - * @param cozeConversationId Coze对话ID - * @param botId Bot ID - * @param workflowId Workflow ID - * @return 是否成功 - */ - boolean updateConversationCozeInfo(String conversationId, String cozeConversationId, String botId, - String workflowId); - - /** - * 更新消息的Coze相关信息 - * - * @param messageId 消息ID - * @param cozeChatId Coze聊天ID - * @param cozeMessageId Coze消息ID - * @param status 状态 - * @return 是否成功 - */ - boolean updateMessageCozeInfo(String messageId, String cozeChatId, String cozeMessageId, String status); - - /** - * 保存Coze API调用记录 - * - * @param cozeApiCall API调用记录 - * @return 保存的记录 - */ - CozeApiCall saveCozeApiCall(CozeApiCall cozeApiCall); - - /** - * 更新Coze API调用记录状态 - * - * @param callId 调用记录ID - * @param status 状态 - * @param responseBody 响应体 - * @param errorMessage 错误信息 - * @return 是否成功 - */ - boolean updateCozeApiCallStatus(String callId, String status, Object responseBody, String errorMessage); - - /** - * 根据ID获取Coze API调用记录 - * - * @param callId 调用记录ID - * @return API调用记录 - */ - CozeApiCall getCozeApiCallById(String callId); - - /** - * 更新Coze API调用记录 - * - * @param cozeApiCall API调用记录 - * @return 是否成功 - */ - boolean updateCozeApiCall(CozeApiCall cozeApiCall); - - /** - * 根据ID列表获取消息 - * - * @param messageIds 消息ID列表 - * @return 消息列表 - */ - List getMessagesByIds(List messageIds); -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/GuestChatService.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/GuestChatService.java deleted file mode 100644 index 6974b8a..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/GuestChatService.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.emotionmuseum.ai.service; - -import com.emotionmuseum.ai.dto.*; -import com.emotionmuseum.common.result.Result; - -import java.util.List; - -/** - * 访客聊天服务接口 - * - * @author huazhongmin - * @since 2025-07-13 - */ -public interface GuestChatService { - - /** - * 访客聊天 - * - * @param request 聊天请求 - * @return 聊天响应 - */ - Result guestChat(GuestChatRequest request); - - /** - * 获取访客会话列表 - * - * @param clientIp 客户端IP - * @param pageNum 页码 - * @param pageSize 页大小 - * @return 会话列表 - */ - Result> getGuestConversations(String clientIp, Integer pageNum, Integer pageSize); - - /** - * 获取访客会话消息列表 - * - * @param conversationId 会话ID - * @param clientIp 客户端IP - * @param pageNum 页码 - * @param pageSize 页大小 - * @return 消息列表 - */ - Result> getGuestConversationMessages(String conversationId, String clientIp, Integer pageNum, Integer pageSize); - - /** - * 结束访客会话 - * - * @param conversationId 会话ID - * @param clientIp 客户端IP - * @return 操作结果 - */ - Result endGuestConversation(String conversationId, String clientIp); - - /** - * 获取或创建访客用户 - * - * @param clientIp 客户端IP - * @param userAgent 用户代理 - * @return 访客用户信息 - */ - Result getOrCreateGuestUser(String clientIp, String userAgent); - - /** - * 访客情绪分析 - * - * @param request 情绪分析请求 - * @param clientIp 客户端IP - * @return 情绪分析结果 - */ - Result analyzeGuestEmotion(EmotionAnalysisRequest request, String clientIp); -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/GuestUserService.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/GuestUserService.java deleted file mode 100644 index 4ae369d..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/GuestUserService.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.emotionmuseum.ai.service; - -import com.emotionmuseum.ai.dto.GuestUserInfo; - -/** - * 访客用户服务接口 - * - * @author huazhongmin - * @since 2025-07-13 - */ -public interface GuestUserService { - - /** - * 根据IP地址获取或创建访客用户 - * - * @param ipAddress 客户端IP地址 - * @param userAgent 用户代理信息 - * @return 访客用户信息 - */ - GuestUserInfo getOrCreateGuestUser(String ipAddress, String userAgent); - - /** - * 根据访客ID获取访客用户信息 - * - * @param guestUserId 访客用户ID - * @return 访客用户信息 - */ - GuestUserInfo getGuestUserById(String guestUserId); - - /** - * 更新访客用户最后活跃时间 - * - * @param guestUserId 访客用户ID - */ - void updateLastActiveTime(String guestUserId); - - /** - * 检查是否为访客用户ID - * - * @param userId 用户ID - * @return 是否为访客用户 - */ - boolean isGuestUser(String userId); - - /** - * 生成访客用户ID - * - * @param ipAddress IP地址 - * @return 访客用户ID - */ - String generateGuestUserId(String ipAddress); -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/impl/AiChatServiceImpl.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/impl/AiChatServiceImpl.java deleted file mode 100644 index 89e6717..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/impl/AiChatServiceImpl.java +++ /dev/null @@ -1,800 +0,0 @@ -package com.emotionmuseum.ai.service.impl; - -import com.emotionmuseum.ai.config.FeatureConfig; -import com.emotionmuseum.ai.dto.*; -import com.emotionmuseum.ai.entity.Conversation; -import com.emotionmuseum.ai.entity.Message; -import com.emotionmuseum.ai.entity.CozeApiCall; -import com.emotionmuseum.ai.service.AiChatService; -import com.emotionmuseum.ai.service.ConversationDbService; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.springframework.web.reactive.function.client.WebClient; - -import java.time.LocalDateTime; -import java.util.*; - -/** - * AI聊天服务实现类 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class AiChatServiceImpl implements AiChatService { - - private final WebClient cozeWebClient; - private final ConversationDbService conversationDbService; - private final FeatureConfig featureConfig; - - @Value("${coze.bot-id}") - private String botId; - - @Value("${coze.workflow-id:}") - private String workflowId; - - @Value("${coze.user-id:emotion-museum-user}") - private String defaultUserId; - - @Override - public CreateConversationResponse createConversation(CreateConversationRequest request) { - log.info("创建会话请求: userId={}, title={}", request.getUserId(), request.getTitle()); - - try { - // 处理用户类型 - String userId = request.getUserId(); - String userType = userId != null && userId.startsWith("guest_") ? "guest" : "registered"; - - // 调用Coze API创建会话 - Map cozeResponse = cozeWebClient.post() - .uri("/v1/conversation/create") - .retrieve() - .bodyToMono(Map.class) - .block(); - - // 创建会话实体 - Conversation conversation = new Conversation(); - conversation.setUserId(userId); - conversation.setUserType(userType); - conversation.setTitle(request.getTitle() != null ? request.getTitle() : "新会话"); - conversation.setType(request.getType()); - conversation.setStatus("active"); - conversation.setInitialMessage(request.getInitialMessage()); - conversation.setContext(request.getContext()); - conversation.setStartTime(LocalDateTime.now()); - conversation.setLastActiveTime(LocalDateTime.now()); - conversation.setMessageCount(0); - conversation.setBotId(botId); - conversation.setWorkflowId(workflowId); - - // 设置客户端信息(访客模式下会有这些信息) - // 这些字段在CreateConversationRequest中可能不存在,暂时跳过 - - // 解析Coze响应获取会话ID - if (cozeResponse != null && cozeResponse.get("data") != null) { - Map data = (Map) cozeResponse.get("data"); - if (data.get("id") != null) { - conversation.setCozeConversationId(data.get("id").toString()); - } - } - - // 保存到数据库 - Conversation savedConversation = conversationDbService.saveConversation(conversation); - - // 构建响应 - CreateConversationResponse response = new CreateConversationResponse(); - response.setConversationId(savedConversation.getId()); - response.setUserId(savedConversation.getUserId()); - response.setTitle(savedConversation.getTitle()); - response.setType(savedConversation.getType()); - response.setStatus(savedConversation.getStatus()); - response.setCozeConversationId(savedConversation.getCozeConversationId()); - response.setCreateTime(savedConversation.getCreateTime()); - response.setUpdateTime(savedConversation.getUpdateTime()); - - log.info("会话创建成功: conversationId={}, cozeConversationId={}", - response.getConversationId(), response.getCozeConversationId()); - return response; - - } catch (Exception e) { - log.error("创建会话失败: userId={}, error={}", request.getUserId(), e.getMessage(), e); - throw new RuntimeException("创建会话失败: " + e.getMessage(), e); - } - } - - @Override - public com.emotionmuseum.ai.dto.ChatResponse chat(ChatRequest request) { - log.info("处理聊天请求: userId={}, message={}", request.getUserId(), request.getMessage()); - - try { - // 构建Coze API请求 - Map cozeRequest = buildCozeRequest(request); - - // 保存用户消息到数据库 - Message userMessage = new Message(); - userMessage.setConversationId(request.getConversationId()); - userMessage.setContent(request.getMessage()); - userMessage.setType(request.getType() != null ? request.getType() : "text"); - userMessage.setSender("user"); - userMessage.setTimestamp(LocalDateTime.now()); - userMessage.setStatus("sent"); - userMessage.setIsRead(1); - Message savedUserMessage = conversationDbService.saveMessage(userMessage); - - // 创建API调用记录 - CozeApiCall apiCall = new CozeApiCall(); - apiCall.setConversationId(request.getConversationId()); - apiCall.setMessageId(savedUserMessage.getId()); - apiCall.setBotId(botId); - apiCall.setWorkflowId(workflowId); - apiCall.setUserId(request.getUserId()); - apiCall.setRequestType("chat"); - apiCall.setRequestUrl("/v3/chat"); - apiCall.setRequestBody((Map) cozeRequest); - // 保存用户消息内容 - apiCall.setUserMessage(request.getMessage()); - apiCall.setUserMessageType("text"); - // 设置客户端信息 - apiCall.setClientIp(getClientIpFromRequest()); - apiCall.setUserAgent(getUserAgentFromRequest()); - apiCall.setSessionId(generateSessionId(request)); - apiCall.setTraceId(generateTraceId()); - apiCall.setStatus("pending"); - apiCall.setStartTime(LocalDateTime.now()); - CozeApiCall savedApiCall = conversationDbService.saveCozeApiCall(apiCall); - - // 调用Coze API - log.info("发送Coze请求: {}", cozeRequest); - Map cozeResponse = cozeWebClient.post() - .uri("/v3/chat") - .bodyValue(cozeRequest) - .retrieve() - .bodyToMono(Map.class) - .block(); - - log.info("收到Coze初始响应: {}", cozeResponse); - - // 解析Coze响应并获取AI回复 - String aiContent = "抱歉,我现在无法理解您的消息。"; - String cozeChatId = null; - String cozeConversationId = null; - - if (cozeResponse != null && cozeResponse.get("data") != null) { - Map data = (Map) cozeResponse.get("data"); - cozeChatId = (String) data.get("id"); - cozeConversationId = (String) data.get("conversation_id"); - - // 更新API调用记录 - conversationDbService.updateCozeApiCallStatus(savedApiCall.getId(), "success", cozeResponse, null); - - if (cozeChatId != null && cozeConversationId != null) { - // 更新会话的Coze信息 - conversationDbService.updateConversationCozeInfo( - request.getConversationId(), cozeConversationId, botId, workflowId); - - // 轮询聊天状态直到完成并获取回复内容 - ChatCompletionResult result = waitForChatCompletionWithResult(cozeChatId, cozeConversationId); - aiContent = result.getContent(); - - // 更新API调用记录 - updateCozeApiCallWithResult(savedApiCall.getId(), result, aiContent); - } - } else { - // 更新API调用记录为失败 - conversationDbService.updateCozeApiCallStatus(savedApiCall.getId(), "failed", null, - "No valid response from Coze API"); - } - - // 保存AI回复消息到数据库(支持拆分多条消息) - List savedAiMessages = saveAiReplyMessages(request.getConversationId(), aiContent, cozeChatId); - Message savedAiMessage = savedAiMessages.get(savedAiMessages.size() - 1); // 获取最后一条消息作为主要回复 - - // 构建响应 - com.emotionmuseum.ai.dto.ChatResponse response = new com.emotionmuseum.ai.dto.ChatResponse(); - response.setMessageId(savedAiMessage.getId()); - response.setConversationId(request.getConversationId()); - response.setContent(aiContent); - response.setTimestamp(savedAiMessage.getTimestamp()); - - // 添加多条消息信息 - if (savedAiMessages.size() > 1) { - response.setMultipleMessages(true); - response.setMessageCount(savedAiMessages.size()); - response.setMessageIds(savedAiMessages.stream() - .map(Message::getId) - .collect(java.util.stream.Collectors.toList())); - log.info("AI回复已拆分为{}条消息: conversationId={}, messageIds={}", - savedAiMessages.size(), request.getConversationId(), response.getMessageIds()); - } else { - response.setMultipleMessages(false); - response.setMessageCount(1); - } - - // 如果需要情绪分析且功能已启用 - if (Boolean.TRUE.equals(request.getNeedEmotionAnalysis()) && - featureConfig.getEmotionAnalysis().isEnabled()) { - try { - EmotionAnalysisRequest emotionRequest = new EmotionAnalysisRequest(); - emotionRequest.setUserId(request.getUserId()); - emotionRequest.setText(request.getMessage()); - response.setEmotionAnalysis(analyzeEmotion(emotionRequest)); - log.debug("情绪分析完成: userId={}", request.getUserId()); - } catch (Exception e) { - log.warn("情绪分析失败,跳过: userId={}, error={}", request.getUserId(), e.getMessage()); - // 情绪分析失败不影响聊天功能 - } - } else if (Boolean.TRUE.equals(request.getNeedEmotionAnalysis())) { - log.debug("情绪分析功能已禁用,跳过分析: userId={}", request.getUserId()); - } - - log.info("聊天响应生成成功: messageId={}", response.getMessageId()); - return response; - - } catch (Exception e) { - log.error("聊天处理失败: userId={}, error={}", request.getUserId(), e.getMessage(), e); - throw new RuntimeException("聊天处理失败: " + e.getMessage(), e); - } - } - - @Override - public EmotionAnalysisResponse analyzeEmotion(EmotionAnalysisRequest request) { - log.info("处理情绪分析请求: userId={}, text={}", request.getUserId(), request.getText()); - - // 检查情绪分析功能是否启用 - if (!featureConfig.getEmotionAnalysis().isEnabled()) { - log.warn("情绪分析功能已禁用: userId={}", request.getUserId()); - throw new RuntimeException("情绪分析功能暂时不可用"); - } - - try { - // 构建情绪分析请求 - Map cozeRequest = new HashMap<>(); - cozeRequest.put("bot_id", botId); - cozeRequest.put("user_id", request.getUserId() != null ? request.getUserId() : defaultUserId); - cozeRequest.put("stream", false); - - String prompt = buildEmotionAnalysisPrompt(request.getText()); - List> messages = new ArrayList<>(); - Map message = new HashMap<>(); - message.put("role", "user"); - message.put("content", prompt); - message.put("content_type", "text"); - messages.add(message); - cozeRequest.put("additional_messages", messages); - - // 调用Coze API - Map cozeResponse = cozeWebClient.post() - .uri("/v3/chat") - .bodyValue(cozeRequest) - .retrieve() - .bodyToMono(Map.class) - .block(); - - // 解析AI返回的情绪分析结果 - String result = ""; - if (cozeResponse != null && cozeResponse.get("data") != null) { - Map data = (Map) cozeResponse.get("data"); - result = extractContentFromCozeResponse(data); - } - - return parseEmotionAnalysisResult(result); - - } catch (Exception e) { - log.error("情绪分析失败: userId={}, error={}", request.getUserId(), e.getMessage(), e); - throw new RuntimeException("情绪分析失败: " + e.getMessage(), e); - } - } - - @Override - public String streamChat(ChatRequest request) { - log.info("处理流式聊天请求: userId={}", request.getUserId()); - - try { - // 构建流式请求 - Map cozeRequest = buildCozeRequest(request); - cozeRequest.put("stream", true); // 启用流式响应 - - log.debug("发送流式Coze请求: {}", cozeRequest); - - // 调用Coze流式API - String streamResponse = cozeWebClient.post() - .uri("/v3/chat") - .bodyValue(cozeRequest) - .retrieve() - .bodyToMono(String.class) - .block(); - - log.debug("收到流式Coze响应: {}", streamResponse); - - // 解析流式响应并提取最终内容 - String finalContent = parseStreamResponse(streamResponse); - - return finalContent != null ? finalContent : "抱歉,流式聊天暂时无法处理您的请求。"; - - } catch (Exception e) { - log.error("流式聊天失败: userId={}, error={}", request.getUserId(), e.getMessage(), e); - // 降级到普通聊天 - try { - com.emotionmuseum.ai.dto.ChatResponse response = chat(request); - return response.getContent(); - } catch (Exception fallbackError) { - log.error("降级聊天也失败: {}", fallbackError.getMessage()); - return "抱歉,聊天服务暂时不可用,请稍后再试。"; - } - } - } - - @Override - public boolean healthCheck() { - try { - // 调用Coze bot信息接口检查健康状态 - Map response = cozeWebClient.get() - .uri("/v1/bot/get_online_info?bot_id=" + botId) - .retrieve() - .bodyToMono(Map.class) - .block(); - - return response != null && response.get("code") != null; - } catch (Exception e) { - log.error("健康检查失败: {}", e.getMessage()); - return false; - } - } - - /** - * 构建Coze API请求 - */ - private Map buildCozeRequest(ChatRequest request) { - Map cozeRequest = new HashMap<>(); - cozeRequest.put("bot_id", botId); - - // 如果有workflow_id,则添加 - if (workflowId != null && !workflowId.trim().isEmpty()) { - cozeRequest.put("workflow_id", workflowId); - } - - cozeRequest.put("user_id", request.getUserId() != null ? request.getUserId() : defaultUserId); - cozeRequest.put("stream", false); - - // 构建消息内容 - String message = request.getMessage(); - if (request.getContext() != null && !request.getContext().trim().isEmpty()) { - message = "上下文: " + request.getContext() + "\n\n用户消息: " + message; - } - - // 添加聊天历史 - List> messages = new ArrayList<>(); - if (request.getHistory() != null && !request.getHistory().isEmpty()) { - for (ChatRequest.ChatMessage historyMsg : request.getHistory()) { - Map msg = new HashMap<>(); - msg.put("role", historyMsg.getRole()); - msg.put("content", historyMsg.getContent()); - msg.put("content_type", "text"); - msg.put("type", "user".equals(historyMsg.getRole()) ? "question" : "answer"); - messages.add(msg); - } - } - - // 添加当前消息 - Map currentMsg = new HashMap<>(); - currentMsg.put("role", "user"); - currentMsg.put("content", message); - currentMsg.put("content_type", "text"); - currentMsg.put("type", "question"); - messages.add(currentMsg); - - cozeRequest.put("additional_messages", messages); - cozeRequest.put("parameters", new HashMap<>()); - - return cozeRequest; - } - - /** - * 聊天完成结果类 - */ - private static class ChatCompletionResult { - private final boolean success; - private final String content; - private final Map finalResponse; - private final String errorMessage; - - public ChatCompletionResult(boolean success, String content, Map finalResponse, - String errorMessage) { - this.success = success; - this.content = content; - this.finalResponse = finalResponse; - this.errorMessage = errorMessage; - } - - public boolean isSuccess() { - return success; - } - - public String getContent() { - return content; - } - - public Map getFinalResponse() { - return finalResponse; - } - - public String getErrorMessage() { - return errorMessage; - } - } - - /** - * 等待聊天完成并获取回复内容(带详细结果) - */ - private ChatCompletionResult waitForChatCompletionWithResult(String chatId, String conversationId) { - try { - // 最多等待30秒,每2秒轮询一次 - int maxAttempts = 15; - int attempt = 0; - - while (attempt < maxAttempts) { - // 检查聊天状态 - log.info("轮询聊天状态,第{}次尝试: chatId={}, conversationId={}", attempt + 1, chatId, conversationId); - Map statusResponse = cozeWebClient.get() - .uri("/v3/chat/retrieve?chat_id={chatId}&conversation_id={conversationId}", - chatId, conversationId) - .retrieve() - .bodyToMono(Map.class) - .block(); - - log.info("轮询响应: {}", statusResponse); - - if (statusResponse != null && statusResponse.get("data") != null) { - Map data = (Map) statusResponse.get("data"); - String status = (String) data.get("status"); - log.info("聊天状态: {}", status); - - if ("completed".equals(status)) { - // 聊天完成,获取消息 - log.info("聊天完成,开始获取消息: chatId={}, conversationId={}", chatId, conversationId); - String content = getChatMessages(chatId, conversationId); - return new ChatCompletionResult(true, content, statusResponse, null); - } else if ("failed".equals(status)) { - log.error("Coze聊天失败: chatId={}, conversationId={}", chatId, conversationId); - return new ChatCompletionResult(false, "抱歉,AI服务暂时不可用,请稍后再试。", statusResponse, "Chat failed"); - } - } else { - log.warn("轮询响应为空或无data字段: {}", statusResponse); - } - - // 等待2秒后重试 - Thread.sleep(2000); - attempt++; - } - - log.warn("Coze聊天超时: chatId={}, conversationId={}", chatId, conversationId); - return new ChatCompletionResult(false, "抱歉,AI响应超时,请稍后再试。", null, "Timeout"); - - } catch (Exception e) { - log.error("等待Coze聊天完成失败: chatId={}, conversationId={}, error={}", - chatId, conversationId, e.getMessage(), e); - return new ChatCompletionResult(false, "抱歉,AI服务出现错误,请稍后再试。", null, e.getMessage()); - } - } - - /** - * 等待聊天完成并获取回复内容 - */ - private String waitForChatCompletion(String chatId, String conversationId) { - ChatCompletionResult result = waitForChatCompletionWithResult(chatId, conversationId); - return result.getContent(); - } - - /** - * 更新Coze API调用记录 - */ - private void updateCozeApiCallWithResult(String apiCallId, ChatCompletionResult result, String aiReply) { - try { - CozeApiCall updateRecord = new CozeApiCall(); - updateRecord.setId(apiCallId); - updateRecord.setEndTime(LocalDateTime.now()); - updateRecord.setAiReply(aiReply); - updateRecord.setAiReplyType("text"); - - if (result.isSuccess()) { - updateRecord.setStatus("success"); - updateRecord.setFinalStatus("completed"); - - // 提取token使用情况 - Map finalResponse = result.getFinalResponse(); - if (finalResponse != null && finalResponse.get("data") != null) { - Map data = (Map) finalResponse.get("data"); - Map usage = (Map) data.get("usage"); - if (usage != null) { - updateRecord.setPromptTokens((Integer) usage.get("input_count")); - updateRecord.setCompletionTokens((Integer) usage.get("output_count")); - updateRecord.setTotalTokens((Integer) usage.get("token_count")); - } - } - } else { - updateRecord.setStatus("failed"); - updateRecord.setFinalStatus("failed"); - updateRecord.setErrorMessage(result.getErrorMessage()); - } - - // 保存最终响应 - updateRecord.setResponseBody(result.getFinalResponse()); - - // 计算耗时 - CozeApiCall originalRecord = conversationDbService.getCozeApiCallById(apiCallId); - if (originalRecord != null && originalRecord.getStartTime() != null) { - long duration = java.time.Duration.between(originalRecord.getStartTime(), updateRecord.getEndTime()) - .toMillis(); - updateRecord.setDurationMs((int) duration); - } - - conversationDbService.updateCozeApiCall(updateRecord); - log.info("更新API调用记录成功: apiCallId={}, status={}, aiReply={}", - apiCallId, updateRecord.getStatus(), - aiReply != null ? aiReply.substring(0, Math.min(50, aiReply.length())) + "..." : "null"); - - } catch (Exception e) { - log.error("更新API调用记录失败: apiCallId={}, error={}", apiCallId, e.getMessage(), e); - } - } - - /** - * 获取客户端IP - */ - private String getClientIpFromRequest() { - // 这里可以从RequestContextHolder获取,暂时返回默认值 - return "127.0.0.1"; - } - - /** - * 获取用户代理 - */ - private String getUserAgentFromRequest() { - // 这里可以从RequestContextHolder获取,暂时返回默认值 - return "EmotionMuseum-Client"; - } - - /** - * 生成会话ID - */ - private String generateSessionId(ChatRequest request) { - return "session_" + request.getUserId() + "_" + System.currentTimeMillis(); - } - - /** - * 生成追踪ID - */ - private String generateTraceId() { - return "trace_" + System.currentTimeMillis() + "_" + (int) (Math.random() * 10000); - } - - /** - * 保存AI回复消息(支持拆分多条消息) - * 当AI回复中包含\n\n或\n时,将消息拆分成多条,模拟真实对话 - */ - public List saveAiReplyMessages(String conversationId, String aiContent, String cozeChatId) { - List savedMessages = new ArrayList<>(); - - if (aiContent == null || aiContent.trim().isEmpty()) { - log.warn("AI回复内容为空,跳过保存"); - return savedMessages; - } - - // 优先按\n\n拆分,如果没有\n\n则按\n拆分 - String[] messageParts; - String splitPattern; - - if (aiContent.contains("\n\n")) { - messageParts = aiContent.split("\\n\\n"); - splitPattern = "\\n\\n"; - log.info("AI回复包含\\n\\n,按双换行拆分为{}条消息: conversationId={}", messageParts.length, conversationId); - } else if (aiContent.contains("\n")) { - messageParts = aiContent.split("\\n"); - splitPattern = "\\n"; - log.info("AI回复包含\\n,按单换行拆分为{}条消息: conversationId={}", messageParts.length, conversationId); - } else { - // 没有换行符,作为单条消息处理 - messageParts = new String[] { aiContent }; - splitPattern = "none"; - log.info("AI回复无换行符,作为单条消息处理: conversationId={}", conversationId); - } - - for (int i = 0; i < messageParts.length; i++) { - String part = messageParts[i].trim(); - if (part.isEmpty()) { - continue; // 跳过空白部分 - } - - Message aiMessage = new Message(); - aiMessage.setConversationId(conversationId); - aiMessage.setContent(part); - aiMessage.setType("text"); - aiMessage.setSender("assistant"); - aiMessage.setTimestamp(LocalDateTime.now().plusSeconds(i)); // 每条消息间隔1秒,模拟真实对话 - aiMessage.setStatus("sent"); - aiMessage.setCozeChatId(cozeChatId); - aiMessage.setIsRead(0); - - // 为拆分的消息添加序号标识和拆分模式 - if (messageParts.length > 1) { - String splitInfo = "none".equals(splitPattern) ? "" : " (按" + splitPattern + "拆分)"; - aiMessage.setRemarks("分段消息 " + (i + 1) + "/" + messageParts.length + splitInfo); - } - - Message savedMessage = conversationDbService.saveMessage(aiMessage); - savedMessages.add(savedMessage); - - log.info("保存AI回复消息片段 {}/{}: messageId={}, content={}", - i + 1, messageParts.length, savedMessage.getId(), - part.length() > 50 ? part.substring(0, 50) + "..." : part); - } - - return savedMessages; - } - - /** - * 获取聊天消息 - */ - private String getChatMessages(String chatId, String conversationId) { - try { - log.info("获取聊天消息: chatId={}, conversationId={}", chatId, conversationId); - Map messagesResponse = cozeWebClient.get() - .uri("/v3/chat/message/list?chat_id={chatId}&conversation_id={conversationId}", - chatId, conversationId) - .retrieve() - .bodyToMono(Map.class) - .block(); - - log.info("消息响应: {}", messagesResponse); - - if (messagesResponse != null && messagesResponse.get("data") != null) { - List> messages = (List>) messagesResponse.get("data"); - log.info("收到{}条消息", messages.size()); - - // 查找AI的回复消息(role=assistant, type=answer) - for (Map message : messages) { - String role = (String) message.get("role"); - String type = (String) message.get("type"); - log.info("消息详情: role={}, type={}, content={}", role, type, message.get("content")); - - if ("assistant".equals(role) && "answer".equals(type)) { - String content = (String) message.get("content"); - log.info("找到AI回复: {}", content); - return content; - } - } - log.warn("未找到AI回复消息"); - } else { - log.warn("消息响应为空或无data字段"); - } - - return "抱歉,未能获取到AI回复。"; - - } catch (Exception e) { - log.error("获取Coze聊天消息失败: chatId={}, conversationId={}, error={}", - chatId, conversationId, e.getMessage(), e); - return "抱歉,获取AI回复失败。"; - } - } - - /** - * 解析流式响应 - */ - private String parseStreamResponse(String streamResponse) { - if (streamResponse == null || streamResponse.trim().isEmpty()) { - return null; - } - - try { - // 流式响应通常是多行JSON,每行一个事件 - String[] lines = streamResponse.split("\n"); - StringBuilder finalContent = new StringBuilder(); - - for (String line : lines) { - line = line.trim(); - if (line.isEmpty() || !line.startsWith("{")) { - continue; - } - - try { - // 这里应该根据Coze实际的流式响应格式来解析 - // 暂时简单处理,实际使用时需要根据API文档调整 - if (line.contains("\"content\"")) { - // 提取content字段 - int contentStart = line.indexOf("\"content\":\"") + 11; - int contentEnd = line.indexOf("\"", contentStart); - if (contentStart > 10 && contentEnd > contentStart) { - String content = line.substring(contentStart, contentEnd); - finalContent.append(content); - } - } - } catch (Exception e) { - log.debug("解析流式响应行失败: {}", line, e); - } - } - - return finalContent.length() > 0 ? finalContent.toString() : null; - - } catch (Exception e) { - log.error("解析流式响应失败: {}", e.getMessage(), e); - return null; - } - } - - /** - * 从Coze响应中提取内容 - */ - private String extractContentFromCozeResponse(Map data) { - try { - // 根据Coze API响应格式解析内容 - if (data.get("messages") != null) { - List> messages = (List>) data.get("messages"); - for (Map message : messages) { - if ("assistant".equals(message.get("role")) && "answer".equals(message.get("type"))) { - return (String) message.get("content"); - } - } - } - return "抱歉,我现在无法理解您的消息。"; - } catch (Exception e) { - log.error("解析Coze响应失败: {}", e.getMessage()); - return "抱歉,响应解析出现问题。"; - } - } - - /** - * 构建情绪分析提示词 - */ - private String buildEmotionAnalysisPrompt(String text) { - return String.format(""" - 请对以下文本进行情绪分析,并以JSON格式返回结果: - - 文本: "%s" - - 请返回以下格式的JSON: - { - "primaryEmotion": "主要情绪", - "intensity": 0.0-1.0的强度值, - "polarity": "positive/negative/neutral", - "confidence": 0.0-1.0的置信度, - "emotions": [ - {"emotion": "情绪名称", "score": 得分, "description": "描述"} - ], - "keywords": ["关键词1", "关键词2"], - "suggestion": "建议" - } - """, text); - } - - /** - * 解析情绪分析结果 - */ - private EmotionAnalysisResponse parseEmotionAnalysisResult(String result) { - // 这里应该解析AI返回的JSON结果 - // 为了简化,先返回一个模拟结果 - EmotionAnalysisResponse response = new EmotionAnalysisResponse(); - response.setPrimaryEmotion("中性"); - response.setIntensity(0.5); - response.setPolarity("neutral"); - response.setConfidence(0.8); - response.setAnalysisTime(LocalDateTime.now()); - - List emotions = new ArrayList<>(); - EmotionAnalysisResponse.EmotionScore score = new EmotionAnalysisResponse.EmotionScore(); - score.setEmotion("中性"); - score.setScore(0.5); - score.setDescription("情绪相对平稳"); - emotions.add(score); - response.setEmotions(emotions); - - response.setKeywords(List.of("情绪", "分析")); - response.setSuggestion("保持当前的情绪状态"); - - return response; - } -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/impl/ConversationDbServiceImpl.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/impl/ConversationDbServiceImpl.java deleted file mode 100644 index aa84254..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/impl/ConversationDbServiceImpl.java +++ /dev/null @@ -1,318 +0,0 @@ -package com.emotionmuseum.ai.service.impl; - -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; -import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; -import com.emotionmuseum.ai.entity.Conversation; -import com.emotionmuseum.ai.entity.Message; -import com.emotionmuseum.ai.entity.CozeApiCall; -import com.emotionmuseum.ai.mapper.ConversationMapper; -import com.emotionmuseum.ai.mapper.MessageMapper; -import com.emotionmuseum.ai.mapper.CozeApiCallMapper; -import com.emotionmuseum.ai.service.ConversationDbService; -import com.emotionmuseum.common.dto.PageQuery; -import com.emotionmuseum.common.entity.BaseEntity; -import com.emotionmuseum.common.util.SnowflakeIdGenerator; -import cn.hutool.core.util.IdUtil; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -/** - * 会话数据库服务实现类 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class ConversationDbServiceImpl implements ConversationDbService { - - private final ConversationMapper conversationMapper; - private final MessageMapper messageMapper; - private final CozeApiCallMapper cozeApiCallMapper; - private final SnowflakeIdGenerator snowflakeIdGenerator; - private final ObjectMapper objectMapper; - - @Override - @Transactional - public Conversation saveConversation(Conversation conversation) { - log.info("保存会话: conversationId={}, userId={}", conversation.getId(), conversation.getUserId()); - - // 手动设置ID,确保不为空 - if (conversation.getId() == null || conversation.getId().isEmpty()) { - conversation.setId(String.valueOf(snowflakeIdGenerator.nextId())); - } - - if (conversation.getStartTime() == null) { - conversation.setStartTime(LocalDateTime.now()); - } - if (conversation.getStatus() == null) { - conversation.setStatus("active"); - } - if (conversation.getMessageCount() == null) { - conversation.setMessageCount(0); - } - if (conversation.getCreateTime() == null) { - conversation.setCreateTime(LocalDateTime.now()); - } - if (conversation.getUpdateTime() == null) { - conversation.setUpdateTime(LocalDateTime.now()); - } - - conversationMapper.insert(conversation); - return conversation; - } - - @Override - public Conversation getConversationById(String conversationId) { - log.debug("查询会话: conversationId={}", conversationId); - return conversationMapper.selectById(conversationId); - } - - @Override - public List getConversationsByUserId(String userId, PageQuery pageQuery) { - log.debug("查询用户会话列表: userId={}, pageNum={}, pageSize={}", - userId, pageQuery.getPageNum(), pageQuery.getPageSize()); - - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Conversation::getUserId, userId) - .orderByDesc(Conversation::getUpdateTime); - - // 简单分页实现 - int offset = (pageQuery.getPageNum() - 1) * pageQuery.getPageSize(); - wrapper.last("LIMIT " + pageQuery.getPageSize() + " OFFSET " + offset); - - return conversationMapper.selectList(wrapper); - } - - @Override - public List getActiveConversationsByUserId(String userId) { - log.debug("查询用户活跃会话: userId={}", userId); - return conversationMapper.selectActiveConversationsByUserId(userId); - } - - @Override - @Transactional - public boolean updateConversationStatus(String conversationId, String status) { - log.info("更新会话状态: conversationId={}, status={}", conversationId, status); - - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(Conversation::getId, conversationId) - .set(Conversation::getStatus, status) - .set(Conversation::getUpdateTime, LocalDateTime.now()); - - if ("ended".equals(status)) { - wrapper.set(Conversation::getEndTime, LocalDateTime.now()); - } - - return conversationMapper.update(null, wrapper) > 0; - } - - @Override - @Transactional - public boolean updateConversationActiveTime(String conversationId) { - log.debug("更新会话活跃时间: conversationId={}", conversationId); - - // 获取当前消息数量 - Integer messageCount = getMessageCount(conversationId); - - return conversationMapper.updateLastActiveTime(conversationId, LocalDateTime.now(), messageCount) > 0; - } - - @Override - @Transactional - public Message saveMessage(Message message) { - log.info("保存消息: conversationId={}, sender={}, type={}", - message.getConversationId(), message.getSender(), message.getType()); - - // 设置消息ID - if (message.getId() == null || message.getId().isEmpty()) { - message.setId(String.valueOf(snowflakeIdGenerator.nextId())); - } - - if (message.getTimestamp() == null) { - message.setTimestamp(LocalDateTime.now()); - } - if (message.getIsRead() == null) { - message.setIsRead(0); - } - - // 手动设置通用字段 - LocalDateTime now = LocalDateTime.now(); - if (message.getCreateTime() == null) { - message.setCreateTime(now); - } - if (message.getUpdateTime() == null) { - message.setUpdateTime(now); - } - - messageMapper.insert(message); - - // 更新会话活跃时间 - updateConversationActiveTime(message.getConversationId()); - - return message; - } - - @Override - public List getMessagesByConversationId(String conversationId, PageQuery pageQuery) { - log.debug("查询会话消息: conversationId={}, pageNum={}, pageSize={}", - conversationId, pageQuery.getPageNum(), pageQuery.getPageSize()); - - int offset = (pageQuery.getPageNum() - 1) * pageQuery.getPageSize(); - return messageMapper.selectMessagesByConversationId(conversationId, pageQuery.getPageSize(), offset); - } - - @Override - public List getLatestMessages(String conversationId, Integer limit) { - log.debug("查询最新消息: conversationId={}, limit={}", conversationId, limit); - return messageMapper.selectLatestMessagesByConversationId(conversationId, limit); - } - - @Override - @Transactional - public boolean markMessageAsRead(String messageId) { - log.debug("标记消息已读: messageId={}", messageId); - return messageMapper.markMessageAsRead(messageId) > 0; - } - - @Override - @Transactional - public boolean markConversationMessagesAsRead(String conversationId) { - log.info("标记会话消息已读: conversationId={}", conversationId); - return messageMapper.markConversationMessagesAsRead(conversationId) > 0; - } - - @Override - public Integer getMessageCount(String conversationId) { - return messageMapper.countMessagesByConversationId(conversationId); - } - - @Override - public Integer getUnreadMessageCount(String conversationId) { - return messageMapper.countUnreadMessages(conversationId); - } - - @Override - @Transactional - public boolean deleteConversation(String conversationId) { - log.info("删除会话: conversationId={}", conversationId); - - // 软删除会话 - LambdaUpdateWrapper conversationWrapper = new LambdaUpdateWrapper<>(); - conversationWrapper.eq(Conversation::getId, conversationId) - .setSql("is_deleted = 1") - .set(Conversation::getUpdateTime, LocalDateTime.now()); - - // 软删除相关消息 - LambdaUpdateWrapper messageWrapper = new LambdaUpdateWrapper<>(); - messageWrapper.eq(Message::getConversationId, conversationId) - .setSql("is_deleted = 1") - .set(Message::getUpdateTime, LocalDateTime.now()); - - boolean conversationDeleted = conversationMapper.update(null, conversationWrapper) > 0; - boolean messagesDeleted = messageMapper.update(null, messageWrapper) >= 0; // 可能没有消息 - - return conversationDeleted && messagesDeleted; - } - - @Override - public Conversation getConversationByCozeId(String cozeConversationId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Conversation::getCozeConversationId, cozeConversationId) - .last("AND is_deleted = 0"); - return conversationMapper.selectOne(wrapper); - } - - @Override - public boolean updateConversationCozeInfo(String conversationId, String cozeConversationId, String botId, - String workflowId) { - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(Conversation::getId, conversationId) - .set(Conversation::getCozeConversationId, cozeConversationId) - .set(Conversation::getBotId, botId) - .set(Conversation::getWorkflowId, workflowId) - .set(Conversation::getUpdateTime, LocalDateTime.now()); - return conversationMapper.update(null, wrapper) > 0; - } - - @Override - public boolean updateMessageCozeInfo(String messageId, String cozeChatId, String cozeMessageId, String status) { - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(Message::getId, messageId) - .set(Message::getCozeChatId, cozeChatId) - .set(Message::getCozeMessageId, cozeMessageId) - .set(Message::getStatus, status) - .set(Message::getUpdateTime, LocalDateTime.now()); - return messageMapper.update(null, wrapper) > 0; - } - - @Override - @Transactional - public CozeApiCall saveCozeApiCall(CozeApiCall cozeApiCall) { - if (cozeApiCall.getId() == null) { - cozeApiCall.setId(IdUtil.fastSimpleUUID()); - } - - // 手动设置通用字段 - LocalDateTime now = LocalDateTime.now(); - if (cozeApiCall.getCreateTime() == null) { - cozeApiCall.setCreateTime(now); - } - if (cozeApiCall.getUpdateTime() == null) { - cozeApiCall.setUpdateTime(now); - } - - cozeApiCallMapper.insert(cozeApiCall); - return cozeApiCall; - } - - @Override - public boolean updateCozeApiCallStatus(String callId, String status, Object responseBody, String errorMessage) { - LocalDateTime now = LocalDateTime.now(); - - if (errorMessage != null) { - // 有错误信息时使用错误更新方法 - return cozeApiCallMapper.updateStatusWithErrorById(callId, status, now, now, errorMessage) > 0; - } else { - // 正常响应时使用响应更新方法,将对象序列化为JSON字符串 - String responseBodyStr = null; - if (responseBody != null) { - try { - responseBodyStr = objectMapper.writeValueAsString(responseBody); - } catch (Exception e) { - log.error("序列化响应体失败: {}", e.getMessage()); - responseBodyStr = responseBody.toString(); - } - } - return cozeApiCallMapper.updateStatusById(callId, status, now, now, responseBodyStr) > 0; - } - } - - @Override - public CozeApiCall getCozeApiCallById(String callId) { - return cozeApiCallMapper.selectById(callId); - } - - @Override - public boolean updateCozeApiCall(CozeApiCall cozeApiCall) { - cozeApiCall.setUpdateTime(LocalDateTime.now()); - return cozeApiCallMapper.updateById(cozeApiCall) > 0; - } - - @Override - public List getMessagesByIds(List messageIds) { - if (messageIds == null || messageIds.isEmpty()) { - return new ArrayList<>(); - } - return messageMapper.selectBatchIds(messageIds); - } -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/impl/GuestChatServiceImpl.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/impl/GuestChatServiceImpl.java deleted file mode 100644 index 22dd647..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/impl/GuestChatServiceImpl.java +++ /dev/null @@ -1,298 +0,0 @@ -package com.emotionmuseum.ai.service.impl; - -import com.emotionmuseum.ai.config.FeatureConfig; -import com.emotionmuseum.ai.dto.*; -import com.emotionmuseum.ai.entity.Conversation; -import com.emotionmuseum.ai.entity.Message; -import com.emotionmuseum.ai.service.AiChatService; -import com.emotionmuseum.ai.service.ConversationDbService; -import com.emotionmuseum.ai.service.GuestChatService; -import com.emotionmuseum.ai.service.GuestUserService; -import com.emotionmuseum.common.result.Result; -import com.emotionmuseum.common.dto.PageQuery; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.stream.Collectors; - -/** - * 访客聊天服务实现 - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class GuestChatServiceImpl implements GuestChatService { - - private final GuestUserService guestUserService; - private final AiChatService aiChatService; - private final ConversationDbService conversationDbService; - private final FeatureConfig featureConfig; - - @Override - public Result guestChat(GuestChatRequest request) { - log.info("处理访客聊天请求: IP={}, Message={}", request.getClientIp(), request.getMessage()); - - try { - // 1. 获取或创建访客用户 - GuestUserInfo guestUser = guestUserService.getOrCreateGuestUser( - request.getClientIp(), request.getUserAgent()); - - // 2. 处理会话 - String conversationId = request.getConversationId(); - boolean isNewConversation = false; - - if (!StringUtils.hasText(conversationId)) { - // 创建新会话 - CreateConversationRequest createRequest = new CreateConversationRequest(); - createRequest.setUserId(guestUser.getGuestUserId()); - createRequest.setTitle(request.getTitle() != null ? request.getTitle() : "访客会话"); - createRequest.setType("guest_chat"); - - CreateConversationResponse createResponse = aiChatService.createConversation(createRequest); - conversationId = createResponse.getConversationId().toString(); - isNewConversation = true; - - log.info("为访客用户创建新会话: guestUserId={}, conversationId={}", - guestUser.getGuestUserId(), conversationId); - } - - // 3. 发送消息 - ChatRequest chatRequest = new ChatRequest(); - chatRequest.setUserId(guestUser.getGuestUserId()); - chatRequest.setConversationId(conversationId); - chatRequest.setMessage(request.getMessage()); - chatRequest.setType(request.getMessageType()); - chatRequest.setNeedEmotionAnalysis(true); - - ChatResponse chatResponse = aiChatService.chat(chatRequest); - - // 4. 更新访客用户统计 - try { - ((GuestUserServiceImpl) guestUserService).incrementMessageCount(guestUser.getGuestUserId(), 2); // 用户消息+AI回复 - guestUserService.updateLastActiveTime(guestUser.getGuestUserId()); - } catch (Exception e) { - log.warn("更新访客用户统计失败: {}", e.getMessage()); - } - - // 5. 构建响应 - GuestChatResponse response = GuestChatResponse.builder() - .guestUserId(guestUser.getGuestUserId()) - .guestNickname(guestUser.getNickname()) - .conversationId(conversationId) - .conversationTitle(request.getTitle()) - .userMessageId(chatResponse.getMessageId()) - .aiMessageId(chatResponse.getMessageId()) - .userMessage(request.getMessage()) - .aiReply(chatResponse.getContent()) - .timestamp(chatResponse.getTimestamp()) - .conversationStatus("active") - .isNewConversation(isNewConversation) - .build(); - - // 6. 添加情绪分析结果(如果有) - if (chatResponse.getEmotionAnalysis() != null) { - response.setEmotionAnalysis(GuestChatResponse.EmotionAnalysisResult.builder() - .primaryEmotion(chatResponse.getEmotionAnalysis().getPrimaryEmotion()) - .emotionScore(chatResponse.getEmotionAnalysis().getIntensity()) - .confidence(chatResponse.getEmotionAnalysis().getConfidence()) - .emotionTrend("stable") - .build()); - } - - // 7. 添加Token使用情况(如果有) - if (chatResponse.getUsage() != null) { - response.setTokenUsage(GuestChatResponse.TokenUsage.builder() - .promptTokens(chatResponse.getUsage().getPromptTokens()) - .completionTokens(chatResponse.getUsage().getCompletionTokens()) - .totalTokens(chatResponse.getUsage().getTotalTokens()) - .build()); - } - - log.info("访客聊天处理成功: guestUserId={}, conversationId={}", - guestUser.getGuestUserId(), conversationId); - - return Result.success(response); - - } catch (Exception e) { - log.error("访客聊天处理失败", e); - return Result.error("聊天处理失败: " + e.getMessage()); - } - } - - @Override - public Result> getGuestConversations(String clientIp, Integer pageNum, - Integer pageSize) { - try { - // 根据IP获取访客用户 - GuestUserInfo guestUser = guestUserService.getOrCreateGuestUser(clientIp, null); - - // 获取访客的会话列表 - PageQuery pageQuery = new PageQuery(); - pageQuery.setPageNum(pageNum); - pageQuery.setPageSize(pageSize); - - List conversations = conversationDbService.getConversationsByUserId( - guestUser.getGuestUserId(), pageQuery); - - List responseList = conversations.stream() - .map(this::convertToConversationListResponse) - .collect(Collectors.toList()); - - return Result.success(responseList); - - } catch (Exception e) { - log.error("获取访客会话列表失败", e); - return Result.error("获取会话列表失败: " + e.getMessage()); - } - } - - @Override - public Result> getGuestConversationMessages(String conversationId, String clientIp, - Integer pageNum, Integer pageSize) { - try { - // 验证会话是否属于该访客用户 - Conversation conversation = conversationDbService.getConversationById(conversationId); - if (conversation == null) { - return Result.error("会话不存在"); - } - - // 验证IP是否匹配 - if (!clientIp.equals(conversation.getClientIp())) { - log.warn("访客IP不匹配: 请求IP={}, 会话IP={}", clientIp, conversation.getClientIp()); - return Result.error("无权访问该会话"); - } - - // 获取消息列表 - PageQuery pageQuery = new PageQuery(); - pageQuery.setPageNum(pageNum); - pageQuery.setPageSize(pageSize); - - List messages = conversationDbService.getMessagesByConversationId( - conversationId, pageQuery); - - List responseList = messages.stream() - .map(this::convertToMessageListResponse) - .collect(Collectors.toList()); - - return Result.success(responseList); - - } catch (Exception e) { - log.error("获取访客会话消息失败", e); - return Result.error("获取会话消息失败: " + e.getMessage()); - } - } - - @Override - public Result endGuestConversation(String conversationId, String clientIp) { - try { - // 验证会话是否属于该访客用户 - Conversation conversation = conversationDbService.getConversationById(conversationId); - if (conversation == null) { - return Result.error("会话不存在"); - } - - // 验证IP是否匹配 - if (!clientIp.equals(conversation.getClientIp())) { - return Result.error("无权操作该会话"); - } - - // 结束会话 - conversationDbService.updateConversationStatus(conversationId, "ended"); - - return Result.success(); - - } catch (Exception e) { - log.error("结束访客会话失败", e); - return Result.error("结束会话失败: " + e.getMessage()); - } - } - - @Override - public Result getOrCreateGuestUser(String clientIp, String userAgent) { - try { - GuestUserInfo guestUser = guestUserService.getOrCreateGuestUser(clientIp, userAgent); - return Result.success(guestUser); - } catch (Exception e) { - log.error("获取访客用户信息失败", e); - return Result.error("获取用户信息失败: " + e.getMessage()); - } - } - - @Override - public Result analyzeGuestEmotion(EmotionAnalysisRequest request, String clientIp) { - // 检查情绪分析功能是否启用 - if (!featureConfig.getEmotionAnalysis().isEnabled()) { - log.warn("访客情绪分析功能已禁用: IP={}", clientIp); - return Result.error("情绪分析功能暂时不可用"); - } - - try { - // 获取访客用户信息 - GuestUserInfo guestUser = guestUserService.getOrCreateGuestUser(clientIp, null); - - // 设置用户ID为访客用户ID - request.setUserId(guestUser.getGuestUserId()); - - // 调用情绪分析服务 - EmotionAnalysisResponse response = aiChatService.analyzeEmotion(request); - - return Result.success(response); - - } catch (Exception e) { - log.error("访客情绪分析失败", e); - return Result.error("情绪分析失败: " + e.getMessage()); - } - } - - /** - * 转换为会话列表响应 - */ - private ConversationListResponse convertToConversationListResponse(Conversation conversation) { - return ConversationListResponse.builder() - .conversationId(conversation.getId()) - .title(conversation.getTitle()) - .type(conversation.getType()) - .status(conversation.getStatus()) - .userId(conversation.getUserId()) - .userType(conversation.getUserType()) - .messageCount(conversation.getMessageCount()) - .lastActiveTime(conversation.getLastActiveTime()) - .createTime(conversation.getCreateTime()) - .primaryEmotion(conversation.getPrimaryEmotion()) - .emotionIntensity( - conversation.getEmotionIntensity() != null ? conversation.getEmotionIntensity().doubleValue() - : null) - .cozeConversationId(conversation.getCozeConversationId()) - .build(); - } - - /** - * 转换为消息列表响应 - */ - private MessageListResponse convertToMessageListResponse(Message message) { - return MessageListResponse.builder() - .messageId(message.getId()) - .conversationId(message.getConversationId()) - .content(message.getContent()) - .type(message.getType()) - .sender(message.getSender()) - .timestamp(message.getTimestamp()) - .status(message.getStatus()) - .emotionType(message.getEmotionType()) - .emotionScore(message.getEmotionScore()) - .emotionConfidence(message.getEmotionConfidence()) - .isRead(message.getIsRead()) - .cozeChatId(message.getCozeChatId()) - .cozeMessageId(message.getCozeMessageId()) - .userId(message.getUserId()) - .userType(message.getUserType()) - .build(); - } -} diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/impl/GuestUserServiceImpl.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/impl/GuestUserServiceImpl.java deleted file mode 100644 index b58a7f9..0000000 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/service/impl/GuestUserServiceImpl.java +++ /dev/null @@ -1,180 +0,0 @@ -package com.emotionmuseum.ai.service.impl; - -import com.emotionmuseum.ai.dto.GuestUserInfo; -import com.emotionmuseum.ai.entity.GuestUser; -import com.emotionmuseum.ai.mapper.GuestUserMapper; -import com.emotionmuseum.ai.service.GuestUserService; -import com.emotionmuseum.common.util.SnowflakeIdGenerator; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; - -import java.security.MessageDigest; -import java.time.LocalDateTime; -import java.util.Random; - -/** - * 访客用户服务实现 - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class GuestUserServiceImpl implements GuestUserService { - - private final GuestUserMapper guestUserMapper; - private final SnowflakeIdGenerator snowflakeIdGenerator; - private final Random random = new Random(); - - @Override - public GuestUserInfo getOrCreateGuestUser(String ipAddress, String userAgent) { - log.info("获取或创建访客用户, IP: {}, UserAgent: {}", ipAddress, userAgent); - - // 先尝试根据IP查找现有访客用户 - GuestUser existingUser = guestUserMapper.findByIpAddress(ipAddress); - - if (existingUser != null) { - // 更新最后活跃时间 - updateLastActiveTime(existingUser.getGuestUserId()); - log.info("找到现有访客用户: {}", existingUser.getGuestUserId()); - return convertToDto(existingUser); - } - - // 创建新的访客用户 - String guestUserId = generateGuestUserId(ipAddress); - GuestUser newUser = new GuestUser(); - // 手动设置ID,确保不为空 - newUser.setId(String.valueOf(snowflakeIdGenerator.nextId())); - newUser.setGuestUserId(guestUserId); - newUser.setIpAddress(ipAddress); - newUser.setUserAgent(userAgent); - newUser.setNickname(generateGuestNickname()); - newUser.setAvatar(generateGuestAvatar()); - newUser.setLastActiveTime(LocalDateTime.now()); - newUser.setConversationCount(0); - newUser.setMessageCount(0); - newUser.setCreateBy("system"); - newUser.setUpdateBy("system"); - - try { - guestUserMapper.insert(newUser); - log.info("创建新访客用户成功: {}", guestUserId); - return convertToDto(newUser); - } catch (Exception e) { - log.error("创建访客用户失败", e); - throw new RuntimeException("创建访客用户失败: " + e.getMessage()); - } - } - - @Override - public GuestUserInfo getGuestUserById(String guestUserId) { - if (!isGuestUser(guestUserId)) { - return null; - } - - GuestUser guestUser = guestUserMapper.findByGuestUserId(guestUserId); - return guestUser != null ? convertToDto(guestUser) : null; - } - - @Override - public void updateLastActiveTime(String guestUserId) { - if (isGuestUser(guestUserId)) { - guestUserMapper.updateLastActiveTime(guestUserId); - } - } - - @Override - public boolean isGuestUser(String userId) { - return StringUtils.hasText(userId) && userId.startsWith("guest_"); - } - - @Override - public String generateGuestUserId(String ipAddress) { - try { - // 使用IP地址和时间戳生成唯一ID - String input = ipAddress + "_" + System.currentTimeMillis() + "_" + random.nextInt(10000); - MessageDigest md = MessageDigest.getInstance("MD5"); - byte[] digest = md.digest(input.getBytes()); - - StringBuilder sb = new StringBuilder(); - for (byte b : digest) { - sb.append(String.format("%02x", b)); - } - - return "guest_" + sb.toString().substring(0, 16); - } catch (Exception e) { - log.error("生成访客用户ID失败", e); - // 降级方案 - return "guest_" + System.currentTimeMillis() + "_" + random.nextInt(10000); - } - } - - /** - * 生成访客昵称 - */ - private String generateGuestNickname() { - String[] adjectives = { "神秘的", "友善的", "智慧的", "温暖的", "勇敢的", "优雅的", "活泼的", "宁静的" }; - String[] nouns = { "访客", "旅行者", "探索者", "朋友", "伙伴", "客人", "用户", "来访者" }; - - String adjective = adjectives[random.nextInt(adjectives.length)]; - String noun = nouns[random.nextInt(nouns.length)]; - int number = random.nextInt(9999) + 1; - - return adjective + noun + number; - } - - /** - * 生成访客头像 - */ - private String generateGuestAvatar() { - // 使用默认头像或随机头像 - String[] avatars = { - "/images/avatars/guest1.png", - "/images/avatars/guest2.png", - "/images/avatars/guest3.png", - "/images/avatars/guest4.png", - "/images/avatars/guest5.png" - }; - - return avatars[random.nextInt(avatars.length)]; - } - - /** - * 转换为DTO - */ - private GuestUserInfo convertToDto(GuestUser guestUser) { - return GuestUserInfo.builder() - .guestUserId(guestUser.getGuestUserId()) - .ipAddress(guestUser.getIpAddress()) - .userAgent(guestUser.getUserAgent()) - .nickname(guestUser.getNickname()) - .avatar(guestUser.getAvatar()) - .createTime(guestUser.getCreateTime()) - .lastActiveTime(guestUser.getLastActiveTime()) - .isGuest(true) - .conversationCount(guestUser.getConversationCount()) - .messageCount(guestUser.getMessageCount()) - .build(); - } - - /** - * 增加会话数量 - */ - public void incrementConversationCount(String guestUserId) { - if (isGuestUser(guestUserId)) { - guestUserMapper.incrementConversationCount(guestUserId); - } - } - - /** - * 增加消息数量 - */ - public void incrementMessageCount(String guestUserId, int count) { - if (isGuestUser(guestUserId)) { - guestUserMapper.incrementMessageCount(guestUserId, count); - } - } -} diff --git a/backend/ai/server/src/main/resources/application-docker.yml b/backend/ai/server/src/main/resources/application-docker.yml deleted file mode 100644 index 2ec57bb..0000000 --- a/backend/ai/server/src/main/resources/application-docker.yml +++ /dev/null @@ -1,89 +0,0 @@ -# AI服务 Docker环境配置 -server: - port: 9002 - -spring: - application: - name: emotion-ai - profiles: - active: docker - cloud: - nacos: - discovery: - server-addr: ${NACOS_SERVER_ADDR:nacos:8848} - namespace: public - group: DEFAULT_GROUP - config: - server-addr: ${NACOS_SERVER_ADDR:nacos:8848} - file-extension: yml - namespace: public - group: DEFAULT_GROUP - datasource: - url: jdbc:mysql://${MYSQL_HOST:mysql}:${MYSQL_PORT:3306}/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: 123456 - driver-class-name: com.mysql.cj.jdbc.Driver - hikari: - pool-name: EmotionAiHikariCP - minimum-idle: 5 - maximum-pool-size: 20 - auto-commit: true - idle-timeout: 30000 - max-lifetime: 1800000 - connection-timeout: 30000 - data: - redis: - host: ${REDIS_HOST:redis} - port: ${REDIS_PORT:6379} - password: - database: 1 - timeout: 6000ms - lettuce: - pool: - max-active: 8 - max-wait: -1ms - max-idle: 8 - min-idle: 0 - -# MyBatis Plus配置 -mybatis-plus: - configuration: - map-underscore-to-camel-case: true - cache-enabled: false - call-setters-on-nulls: true - jdbc-type-for-null: 'null' - log-impl: org.apache.ibatis.logging.stdout.StdOutImpl - global-config: - db-config: - id-type: assign_uuid - logic-delete-field: isDeleted - logic-delete-value: 1 - logic-not-delete-value: 0 - banner: false - -# Coze API配置 -coze: - api: - base-url: https://api.coze.cn - token: ${COZE_API_TOKEN:your-coze-api-token} - bot-id: 7523042446285439016 - workflow-id: 7523047462895796287 - timeout: 30000 - -# 日志配置 -logging: - level: - com.emotionmuseum: DEBUG - com.emotionmuseum.ai.mapper: DEBUG - pattern: - console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%logger{50}] - %msg%n" - -# 管理端点 -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus - endpoint: - health: - show-details: always diff --git a/backend/ai/server/src/main/resources/application-local.yml b/backend/ai/server/src/main/resources/application-local.yml deleted file mode 100644 index e74d4b3..0000000 --- a/backend/ai/server/src/main/resources/application-local.yml +++ /dev/null @@ -1,82 +0,0 @@ -# 本地开发环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: localhost:8848 - namespace: - group: DEFAULT_GROUP - enabled: true - username: nacos - password: Peanut2817*# - metadata: - version: 1.0.0 - zone: local - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: localhost:8848 - namespace: - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: Peanut2817*# - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: 123456 - - # Redis配置 - data: - redis: - host: localhost - port: 6379 - password: - database: 0 - -# Coze平台配置 -coze: - base-url: https://api.coze.cn - api-key: your-coze-api-key - bot-id: 7523042446285439016 - workflow-id: 7523047462895796287 - user-id: emotion-museum-user - token: pat_GCR4qKzqpf90wMCvKsldMrB18KG3QsLDci65bZthssKsbLxu8X70BKYumleDcabO - timeout: 60 - max-retries: 3 - stream: false - model: - temperature: 0.7 - max-tokens: 1000 - top-p: 0.9 - frequency-penalty: 0.0 - presence-penalty: 0.0 - -# 功能开关配置 -features: - emotion-analysis: - enabled: false - auto-analyze: false - chat: - enabled: true - stream: false - -# 日志配置 -logging: - level: - com.emotionmuseum: debug - com.baomidou.mybatisplus: debug - com.alibaba.nacos: info - file: - name: logs/emotion-ai-local.log diff --git a/backend/ai/server/src/main/resources/application-prod.yml b/backend/ai/server/src/main/resources/application-prod.yml deleted file mode 100644 index fd8472c..0000000 --- a/backend/ai/server/src/main/resources/application-prod.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 生产环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: prod - group: DEFAULT_GROUP - enabled: true - username: nacos - password: EmotionMuseum2025 - metadata: - version: 1.0.0 - zone: prod - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: 101.200.208.45:8848 - namespace: prod - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: EmotionMuseum2025 - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://101.200.208.45:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: EmotionMuseum2025*# - - # Redis配置 - data: - redis: - host: 101.200.208.45 - port: 6379 - password: EmotionMuseum2025*# - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: warn - com.baomidou.mybatisplus: warn - com.alibaba.nacos: error - file: - name: logs/emotion-ai-prod.log diff --git a/backend/ai/server/src/main/resources/application-test.yml b/backend/ai/server/src/main/resources/application-test.yml deleted file mode 100644 index 37677da..0000000 --- a/backend/ai/server/src/main/resources/application-test.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 测试环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: test - group: DEFAULT_GROUP - enabled: true - username: nacos - password: EmotionMuseum2025 - metadata: - version: 1.0.0 - zone: test - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: 101.200.208.45:8848 - namespace: test - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: EmotionMuseum2025 - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://101.200.208.45:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: EmotionMuseum2025*# - - # Redis配置 - data: - redis: - host: 101.200.208.45 - port: 6379 - password: EmotionMuseum2025*# - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: info - com.baomidou.mybatisplus: info - com.alibaba.nacos: warn - file: - name: logs/emotion-ai-test.log diff --git a/backend/ai/server/src/main/resources/application.yml b/backend/ai/server/src/main/resources/application.yml deleted file mode 100644 index 1ca50da..0000000 --- a/backend/ai/server/src/main/resources/application.yml +++ /dev/null @@ -1,128 +0,0 @@ -server: - port: 19002 - -spring: - application: - name: emotion-ai - - # 配置文件激活 - profiles: - active: ${SPRING_PROFILES_ACTIVE:local} - - # 允许Bean覆盖和循环引用 - main: - allow-bean-definition-overriding: true - allow-circular-references: true - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:emotion_museum}?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: ${MYSQL_USERNAME:root} - password: ${MYSQL_PASSWORD:123456} - hikari: - minimum-idle: 5 - maximum-pool-size: 20 - idle-timeout: 30000 - max-lifetime: 1800000 - connection-timeout: 30000 - connection-test-query: SELECT 1 - - # Redis配置 - data: - redis: - host: ${REDIS_HOST:localhost} - port: ${REDIS_PORT:6379} - password: ${REDIS_PASSWORD:} - database: 1 - timeout: 10000ms - lettuce: - pool: - max-active: 8 - max-wait: -1ms - max-idle: 8 - min-idle: 0 - # Nacos配置 - cloud: - nacos: - discovery: - server-addr: ${NACOS_HOST:localhost}:${NACOS_PORT:8848} - namespace: ${NACOS_NAMESPACE:} - group: ${NACOS_GROUP:DEFAULT_GROUP} - enabled: ${NACOS_DISCOVERY_ENABLED:false} - config: - server-addr: ${NACOS_HOST:localhost}:${NACOS_PORT:8848} - namespace: ${NACOS_NAMESPACE:} - group: ${NACOS_GROUP:DEFAULT_GROUP} - file-extension: yml - enabled: ${NACOS_CONFIG_ENABLED:false} - - - -# Coze平台配置 -coze: - base-url: ${COZE_BASE_URL:https://api.coze.cn} - api-key: ${COZE_API_KEY:your-coze-api-key} - bot-id: ${COZE_BOT_ID:7523042446285439016} - workflow-id: ${COZE_WORKFLOW_ID:7523047462895796287} - user-id: ${COZE_USER_ID:emotion-museum-user} - token: pat_GCR4qKzqpf90wMCvKsldMrB18KG3QsLDci65bZthssKsbLxu8X70BKYumleDcabO - timeout: 60 - max-retries: 3 - stream: false - model: - temperature: 0.7 - max-tokens: 1000 - top-p: 0.9 - frequency-penalty: 0.0 - presence-penalty: 0.0 - -# 功能开关配置 -features: - emotion-analysis: - enabled: ${EMOTION_ANALYSIS_ENABLED:false} # 暂时禁用情绪分析 - auto-analyze: false # 禁用自动情绪分析 - chat: - enabled: true - stream: false - -# MyBatis Plus配置 -mybatis-plus: - configuration: - map-underscore-to-camel-case: true - cache-enabled: false - call-setters-on-nulls: true - jdbc-type-for-null: 'null' - global-config: - db-config: - id-type: ASSIGN_UUID - logic-delete-field: is_deleted - logic-delete-value: 1 - logic-not-delete-value: 0 - mapper-locations: classpath*:/mapper/**/*.xml - -# 监控配置 -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus - endpoint: - health: - show-details: always - metrics: - export: - prometheus: - enabled: true - -# 日志配置 -logging: - file: - path: /data/logs/emotion-museum/ai - level: - com.emotionmuseum: debug - com.baomidou.mybatisplus: debug - com.emotionmuseum.common.handler.MetaObjectHandler: debug - com.emotionmuseum.common.interceptor.UserContextInterceptor: debug - pattern: - console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%logger{50}] - %msg%n" diff --git a/backend/ai/server/src/test/java/com/emotionmuseum/ai/service/MessageSplitTest.java b/backend/ai/server/src/test/java/com/emotionmuseum/ai/service/MessageSplitTest.java deleted file mode 100644 index 292b1d4..0000000 --- a/backend/ai/server/src/test/java/com/emotionmuseum/ai/service/MessageSplitTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.emotionmuseum.ai.service; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -/** - * 消息拆分功能测试 - */ -@SpringBootTest -public class MessageSplitTest { - - @Test - public void testMessageSplit() { - // 测试消息拆分逻辑 - String aiContent = "这是第一段内容,介绍了基本功能。\n\n这是第二段内容,详细说明了聊天功能。\n\n这是第三段内容,介绍了情感分析功能。"; - - // 按\n\n拆分消息 - String[] messageParts = aiContent.split("\\n\\n"); - - System.out.println("原始消息: " + aiContent); - System.out.println("拆分后的消息数量: " + messageParts.length); - - for (int i = 0; i < messageParts.length; i++) { - String part = messageParts[i].trim(); - if (!part.isEmpty()) { - System.out.println("消息片段 " + (i + 1) + ": " + part); - } - } - } -} diff --git a/backend/auth/Dockerfile b/backend/auth/Dockerfile deleted file mode 100644 index 3b20a40..0000000 --- a/backend/auth/Dockerfile +++ /dev/null @@ -1,48 +0,0 @@ -# 认证服务Dockerfile -FROM openjdk:17-jdk-alpine - -# 设置工作目录 -WORKDIR /app - -# 安装必要的工具 -RUN apk add --no-cache curl tzdata && \ - cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ - echo "Asia/Shanghai" > /etc/timezone - -# 复制Maven构建文件 -COPY pom.xml ./ -COPY emotion-common ./emotion-common -COPY emotion-auth ./emotion-auth - -# 安装Maven -RUN apk add --no-cache maven - -# 构建应用 -RUN mvn clean package -DskipTests -pl emotion-auth -am - -# 创建运行用户 -RUN addgroup -g 1000 emotion && \ - adduser -D -s /bin/sh -u 1000 -G emotion emotion - -# 复制jar文件 -RUN cp emotion-auth/target/emotion-auth-*.jar app.jar - -# 设置文件权限 -RUN chown -R emotion:emotion /app - -# 切换到非root用户 -USER emotion - -# 健康检查 -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ - CMD curl -f http://localhost:19008/actuator/health || exit 1 - -# 暴露端口 -EXPOSE 19008 - -# 启动命令 -ENTRYPOINT ["java", "-jar", \ - "-Xms512m", "-Xmx1024m", \ - "-Djava.security.egd=file:/dev/./urandom", \ - "-Dspring.profiles.active=local", \ - "app.jar"] diff --git a/backend/auth/api/pom.xml b/backend/auth/api/pom.xml deleted file mode 100644 index 77cd614..0000000 --- a/backend/auth/api/pom.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - auth - 1.0.0 - ../pom.xml - - auth-api - auth-api - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - com.emotionmuseum - common - ${project.version} - - - diff --git a/backend/auth/api/src/main/java/com/emotionmuseum/auth/api/client/AuthClient.java b/backend/auth/api/src/main/java/com/emotionmuseum/auth/api/client/AuthClient.java deleted file mode 100644 index 403047d..0000000 --- a/backend/auth/api/src/main/java/com/emotionmuseum/auth/api/client/AuthClient.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.emotionmuseum.auth.api.client; - -import com.emotionmuseum.common.result.Result; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; - -@FeignClient(name = "auth") -public interface AuthClient { - - @GetMapping("/auth/health") - Result health(); -} - - diff --git a/backend/auth/deploy.sh b/backend/auth/deploy.sh deleted file mode 100644 index 26b28b2..0000000 --- a/backend/auth/deploy.sh +++ /dev/null @@ -1,226 +0,0 @@ -#!/bin/bash - -# emotion-auth 单独部署脚本 -# 作者: emotion-museum -# 日期: 2025-07-18 - -set -e - -# 配置变量 -SERVICE_NAME="emotion-auth" -SERVICE_PORT="" -REMOTE_HOST="'root@101.200.208.45'" -REMOTE_BUILD_DIR="/data/builds" -REMOTE_DOCKER_COMPOSE_DIR="/data/docker" -PROFILE="test" -PROJECT_NAME="emotion-museum" - -# 颜色输出 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -# 日志函数 -log_info() { - echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -# 检查远程服务器连接 -check_remote_connection() { - log_info "检查远程服务器连接..." - if ssh -o ConnectTimeout=10 'root@101.200.208.45' "echo 'Connection successful'" > /dev/null 2>&1; then - log_success "远程服务器连接正常" - else - log_error "无法连接到远程服务器 'root@101.200.208.45'" - exit 1 - fi -} - -# 构建服务 -build_service() { - log_info "构建服务: $SERVICE_NAME" - - # 构建父项目依赖 - cd .. - mvn clean install -DskipTests -q - cd emotion-auth - - # 构建当前服务 - if mvn clean package -DskipTests -Ptest -q; then - log_success "服务 $SERVICE_NAME 构建成功" - else - log_error "服务 $SERVICE_NAME 构建失败" - exit 1 - fi -} - -# 创建Dockerfile -create_dockerfile() { - log_info "创建Dockerfile: $SERVICE_NAME" - - ssh 'root@101.200.208.45' "cat > $REMOTE_DOCKER_COMPOSE_DIR/Dockerfile.${SERVICE_NAME} << 'EOF' -# 使用阿里云镜像源的OpenJDK -# 使用Java 17 Alpine镜像 -FROM openjdk:17-alpine - -WORKDIR /app - -# 安装必要的工具 (Alpine Linux使用apk) -RUN apk add --no-cache curl - -COPY ${SERVICE_NAME}-1.0.0.jar app.jar - -RUN mkdir -p /app/logs - -ENV TZ=Asia/Shanghai -RUN ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone - -EXPOSE ${SERVICE_PORT} - -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \\ - CMD curl -f http://localhost:${SERVICE_PORT}/actuator/health || exit 1 - -ENTRYPOINT [\"java\", \"-Djava.security.egd=file:/dev/./urandom\", \"-Xms512m\", \"-Xmx1024m\", \"-jar\", \"app.jar\"] -EOF" -} - -# 部署服务 -deploy_service() { - log_info "开始部署服务: $SERVICE_NAME" - - # 检查jar包 - local jar_file="target/${SERVICE_NAME}-1.0.0.jar" - if [ ! -f "$jar_file" ]; then - log_error "JAR包不存在: $jar_file" - exit 1 - fi - - # 创建远程目录 - ssh 'root@101.200.208.45' " - mkdir -p $REMOTE_BUILD_DIR - mkdir -p $REMOTE_DOCKER_COMPOSE_DIR - mkdir -p /data/logs/emotion-museum - " - - # 删除旧jar包 - log_info "删除远程旧jar包" - ssh 'root@101.200.208.45' "rm -f $REMOTE_BUILD_DIR/${SERVICE_NAME}-*.jar" - - # 上传新jar包 - log_info "上传jar包" - if scp "$jar_file" 'root@101.200.208.45':$REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar; then - log_success "jar包上传成功" - else - log_error "jar包上传失败" - exit 1 - fi - - # 创建Dockerfile - create_dockerfile - - # 停止旧容器 - log_info "停止旧容器" - ssh 'root@101.200.208.45' " - docker stop ${SERVICE_NAME} 2>/dev/null || true - docker rm ${SERVICE_NAME} 2>/dev/null || true - docker rmi ${PROJECT_NAME}/${SERVICE_NAME}:latest 2>/dev/null || true - " - - # 创建Docker网络 - ssh 'root@101.200.208.45' "docker network create emotion-network 2>/dev/null || true" - - # 构建镜像 - log_info "构建Docker镜像" - ssh 'root@101.200.208.45' " - cd $REMOTE_DOCKER_COMPOSE_DIR - # 复制jar包到Docker构建目录 - cp $REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar $REMOTE_DOCKER_COMPOSE_DIR/ - - # 构建镜像 docker build -t ${PROJECT_NAME}/${SERVICE_NAME}:latest -f Dockerfile.${SERVICE_NAME} . - - # 清理临时文件 - rm -f ${SERVICE_NAME}-1.0.0.jar " - - # 启动容器 - log_info "启动新容器" - ssh 'root@101.200.208.45' " - docker run -d \\ - --name ${SERVICE_NAME} \\ - --network emotion-network \\ - -p ${SERVICE_PORT}:${SERVICE_PORT} \\ - -v /data/logs/emotion-museum:/app/logs \\ - -e SPRING_PROFILES_ACTIVE=${PROFILE} \\ - -e MYSQL_HOST=101.200.208.45 \\ - -e MYSQL_PORT=3306 \\ - -e MYSQL_DATABASE=emotion_museum \\ - -e MYSQL_USERNAME=root \\ - -e MYSQL_PASSWORD='EmotionMuseum2025*#' \\ - -e REDIS_HOST=101.200.208.45 \\ - -e REDIS_PORT=6379 \\ - -e REDIS_PASSWORD= \\ - -e REDIS_DATABASE=0 \\ - -e NACOS_SERVER_ADDR=101.200.208.45:8848 \\ - -e NACOS_USERNAME=nacos \\ - -e NACOS_PASSWORD='Peanut2817*#' \\ - --restart unless-stopped \\ - ${PROJECT_NAME}/${SERVICE_NAME}:latest - " - - # 等待启动 - log_info "等待服务启动..." - sleep 15 - - # 检查状态 - if ssh 'root@101.200.208.45' "docker ps | grep ${SERVICE_NAME}" > /dev/null; then - log_success "服务启动成功" - - # 显示日志 - log_info "服务日志 最后20行:" - ssh 'root@101.200.208.45' "docker logs --tail 20 ${SERVICE_NAME}" - - # 健康检查 - log_info "执行健康检查..." - sleep 10 - if ssh 'root@101.200.208.45' "curl -f -s http://localhost:${SERVICE_PORT}/actuator/health" > /dev/null 2>&1; then - log_success "健康检查通过" - else - log_warning "健康检查失败,服务可能仍在启动中" - fi - else - log_error "服务启动失败" - ssh 'root@101.200.208.45' "docker logs ${SERVICE_NAME}" - exit 1 - fi -} - -# 主函数 -main() { - log_info "开始部署 $SERVICE_NAME 服务" - log_info "目标服务器: $REMOTE_HOST" - log_info "服务端口: $SERVICE_PORT" - log_info "部署环境: $PROFILE" - - check_remote_connection - build_service - deploy_service - - log_success "$SERVICE_NAME 服务部署完成!" - log_info "访问地址: http://101.200.208.45:$SERVICE_PORT" -} - -# 执行主函数 -main "$@" diff --git a/backend/auth/pom.xml b/backend/auth/pom.xml deleted file mode 100644 index ff0e290..0000000 --- a/backend/auth/pom.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - backend - 1.0.0 - ../pom.xml - - auth - pom - - api - server - - diff --git a/backend/auth/server/pom.xml b/backend/auth/server/pom.xml deleted file mode 100644 index f207db6..0000000 --- a/backend/auth/server/pom.xml +++ /dev/null @@ -1,145 +0,0 @@ - - - 4.0.0 - - - com.emotionmuseum - auth - 1.0.0 - ../pom.xml - - - auth-server - auth - 情感博物馆认证授权服务 - - - - com.emotionmuseum - auth-api - ${project.version} - - - - - com.emotionmuseum - common - ${project.version} - - - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.boot - spring-boot-starter-security - - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - - org.springframework.boot - spring-boot-starter-data-redis - - - - - mysql - mysql-connector-java - runtime - - - - - com.baomidou - mybatis-plus-boot-starter - - - - - io.jsonwebtoken - jjwt-api - - - io.jsonwebtoken - jjwt-impl - - - io.jsonwebtoken - jjwt-jackson - - - - - me.zhyd.oauth - JustAuth - 1.16.5 - - - - - com.github.whvcse - easy-captcha - 1.6.2 - - - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-discovery - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-config - - - - - org.springframework.boot - spring-boot-starter-actuator - - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.security - spring-security-test - test - - - - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring-boot.version} - - com.emotionmuseum.auth.AuthApplication - - - - - repackage - - - - - - - \ No newline at end of file diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/AuthApplication.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/AuthApplication.java deleted file mode 100644 index dd8b87c..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/AuthApplication.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.emotionmuseum.auth; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; - -/** - * 认证服务启动类 - * - * @author huazhongmin - * @since 2025-07-16 - */ -@SpringBootApplication(scanBasePackages = {"com.emotionmuseum"}) -@EnableDiscoveryClient -@MapperScan("com.emotionmuseum.auth.mapper") -public class AuthApplication { - - public static void main(String[] args) { - SpringApplication.run(AuthApplication.class, args); - } -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/config/CaptchaConfig.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/config/CaptchaConfig.java deleted file mode 100644 index 652bc1c..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/config/CaptchaConfig.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.emotionmuseum.auth.config; - -import com.wf.captcha.ChineseCaptcha; -import com.wf.captcha.GifCaptcha; -import com.wf.captcha.SpecCaptcha; -import com.wf.captcha.base.Captcha; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * 验证码配置 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Configuration -public class CaptchaConfig { - - /** - * 算术验证码 - 暂时禁用,因为Java 23中JavaScript引擎问题 - */ - // @Bean("arithmeticCaptcha") - // public Captcha arithmeticCaptcha() { - // ArithmeticCaptcha captcha = new ArithmeticCaptcha(130, 48); - // captcha.setLen(2); // 几位数运算,默认是两位 - // // captcha.getArithmeticString(); // 获取运算的公式:3+2=? - // return captcha; - // } - - /** - * 中文验证码 - */ - @Bean("chineseCaptcha") - public Captcha chineseCaptcha() { - ChineseCaptcha captcha = new ChineseCaptcha(130, 48); - captcha.setLen(4); // 几个汉字,默认5个 - return captcha; - } - - /** - * GIF验证码 - */ - @Bean("gifCaptcha") - public Captcha gifCaptcha() { - GifCaptcha captcha = new GifCaptcha(130, 48); - captcha.setLen(4); // 几位数字,默认5位 - return captcha; - } - - /** - * PNG验证码 - */ - @Bean("specCaptcha") - public Captcha specCaptcha() { - SpecCaptcha captcha = new SpecCaptcha(130, 48, 4); - captcha.setCharType(Captcha.TYPE_DEFAULT); // 设置类型,纯数字、纯字母、字母数字混合 - return captcha; - } -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/config/OAuthConfig.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/config/OAuthConfig.java deleted file mode 100644 index b0ab821..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/config/OAuthConfig.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.emotionmuseum.auth.config; - -import me.zhyd.oauth.config.AuthConfig; -import me.zhyd.oauth.request.*; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * 第三方登录配置 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Configuration -@ConditionalOnProperty(name = "oauth.enabled", havingValue = "true", matchIfMissing = false) -public class OAuthConfig { - - @Value("${oauth.wechat.client-id:}") - private String wechatClientId; - - @Value("${oauth.wechat.client-secret:}") - private String wechatClientSecret; - - @Value("${oauth.wechat.redirect-uri:}") - private String wechatRedirectUri; - - @Value("${oauth.qq.client-id:}") - private String qqClientId; - - @Value("${oauth.qq.client-secret:}") - private String qqClientSecret; - - @Value("${oauth.qq.redirect-uri:}") - private String qqRedirectUri; - - @Value("${oauth.wechat-mp.client-id:}") - private String wechatMpClientId; - - @Value("${oauth.wechat-mp.client-secret:}") - private String wechatMpClientSecret; - - @Value("${oauth.wechat-mp.redirect-uri:}") - private String wechatMpRedirectUri; - - /** - * 微信开放平台登录 - */ - @Bean - @ConditionalOnProperty(name = "oauth.wechat.client-id", matchIfMissing = false) - public AuthWeChatOpenRequest weChatOpenRequest() { - return new AuthWeChatOpenRequest(AuthConfig.builder() - .clientId(wechatClientId) - .clientSecret(wechatClientSecret) - .redirectUri(wechatRedirectUri) - .build()); - } - - /** - * 微信公众平台登录 - */ - @Bean - @ConditionalOnProperty(name = "oauth.wechat-mp.client-id", matchIfMissing = false) - public AuthWeChatMpRequest weChatMpRequest() { - return new AuthWeChatMpRequest(AuthConfig.builder() - .clientId(wechatMpClientId) - .clientSecret(wechatMpClientSecret) - .redirectUri(wechatMpRedirectUri) - .build()); - } - - /** - * QQ登录 - */ - @Bean - @ConditionalOnProperty(name = "oauth.qq.client-id", matchIfMissing = false) - public AuthQqRequest qqRequest() { - return new AuthQqRequest(AuthConfig.builder() - .clientId(qqClientId) - .clientSecret(qqClientSecret) - .redirectUri(qqRedirectUri) - .build()); - } -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/config/RedisConfig.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/config/RedisConfig.java deleted file mode 100644 index 47c7dff..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/config/RedisConfig.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.emotionmuseum.auth.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; - -/** - * Redis配置类 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Configuration -public class RedisConfig { - - /** - * 配置RedisTemplate - */ - @Bean - public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { - RedisTemplate template = new RedisTemplate<>(); - template.setConnectionFactory(connectionFactory); - - // 使用String序列化器作为key的序列化器 - StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); - template.setKeySerializer(stringRedisSerializer); - template.setHashKeySerializer(stringRedisSerializer); - - // 使用JSON序列化器作为value的序列化器 - GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); - template.setValueSerializer(jsonRedisSerializer); - template.setHashValueSerializer(jsonRedisSerializer); - - template.afterPropertiesSet(); - return template; - } -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/config/SecurityConfig.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/config/SecurityConfig.java deleted file mode 100644 index 868c1c8..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/config/SecurityConfig.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.emotionmuseum.auth.config; - -import com.emotionmuseum.auth.security.JwtAuthenticationFilter; -import com.emotionmuseum.auth.security.UserDetailsServiceImpl; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; -import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; -import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -import java.util.Arrays; -import java.util.Collections; - -/** - * Spring Security配置类 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Configuration -@EnableWebSecurity -@EnableMethodSecurity -@RequiredArgsConstructor -public class SecurityConfig { - - private final UserDetailsServiceImpl userDetailsService; - private final JwtAuthenticationFilter jwtAuthenticationFilter; - - /** - * 密码编码器 - */ - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - - /** - * 认证提供者 - */ - @Bean - public AuthenticationProvider authenticationProvider() { - DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); - authProvider.setUserDetailsService(userDetailsService); - authProvider.setPasswordEncoder(passwordEncoder()); - return authProvider; - } - - /** - * 认证管理器 - */ - @Bean - public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { - return config.getAuthenticationManager(); - } - - /** - * CORS配置 - */ - @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOriginPatterns(Collections.singletonList("*")); - configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); - configuration.setAllowedHeaders(Collections.singletonList("*")); - configuration.setAllowCredentials(true); - configuration.setMaxAge(3600L); - - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", configuration); - return source; - } - - /** - * 安全过滤器链 - */ - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - // 禁用CSRF - .csrf(AbstractHttpConfigurer::disable) - - // 配置CORS - .cors(cors -> cors.configurationSource(corsConfigurationSource())) - - // 配置会话管理 - .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - - // 配置授权规则 - .authorizeHttpRequests(authz -> authz - // 公开接口 - .requestMatchers( - "/auth/register", - "/auth/login", - "/auth/refresh", - "/auth/check-account", - "/auth/check-email", - "/auth/check-phone", - "/auth/resetPassword", - "/captcha/**", - "/oauth/**") - .permitAll() - - // 监控和文档接口 - .requestMatchers( - "/actuator/**", - "/swagger-ui/**", - "/v3/api-docs/**", - "/doc.html", - "/swagger-resources/**", - "/webjars/**", - "/error") - .permitAll() - - // 其他接口需要认证 - .anyRequest().authenticated()) - - // 配置认证提供者 - .authenticationProvider(authenticationProvider()) - - // 添加JWT过滤器 - .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - - return http.build(); - } -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/controller/AuthController.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/controller/AuthController.java deleted file mode 100644 index 79413a5..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/controller/AuthController.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.emotionmuseum.auth.controller; - -import com.emotionmuseum.common.result.Result; -import com.emotionmuseum.auth.request.LoginRequest; -import com.emotionmuseum.auth.request.RegisterRequest; -import com.emotionmuseum.auth.request.ResetPasswordRequest; -import com.emotionmuseum.auth.service.AuthService; -import com.emotionmuseum.auth.response.LoginResponse; -import com.emotionmuseum.auth.response.ResetPasswordResponse; -import com.emotionmuseum.auth.response.UserInfoResponse; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.validation.Valid; - -/** - * 认证控制器 - * - * @author huazhongmin - * @since 2025-07-16 - */ -@Slf4j -@RestController -@RequestMapping("/auth") -@RequiredArgsConstructor -@Validated -@Tag(name = "用户认证", description = "用户注册、登录、认证管理") -public class AuthController { - - private final AuthService authService; - - @Operation(summary = "用户注册") - @PostMapping("/register") - public Result register(@Valid @RequestBody RegisterRequest request) { - log.info("用户注册请求: {}", request.getAccount()); - UserInfoResponse response = authService.register(request); - return Result.success("注册成功", response); - } - - @Operation(summary = "用户登录") - @PostMapping("/login") - public Result login(@Valid @RequestBody LoginRequest request) { - log.info("用户登录请求: {}", request.getAccount()); - LoginResponse response = authService.login(request); - return Result.success("登录成功", response); - } - - @Operation(summary = "刷新Token") - @PostMapping("/refresh") - public Result refreshToken( - @Parameter(description = "刷新Token") @RequestParam String refreshToken) { - log.info("刷新Token请求"); - LoginResponse response = authService.refreshToken(refreshToken); - return Result.success("Token刷新成功", response); - } - - @Operation(summary = "用户登出") - @PostMapping("/logout") - public Result logout( - @Parameter(description = "用户ID") @RequestParam String userId) { - log.info("用户登出请求: {}", userId); - authService.logout(userId); - return Result.success(); - } - - @Operation(summary = "验证Token") - @GetMapping("/validate-token") - public Result validateToken() { - log.info("验证Token请求"); - // 如果能到达这里,说明token有效(通过了JWT过滤器) - return Result.success("Token有效", true); - } - - @Operation(summary = "获取当前用户信息") - @GetMapping("/user-info") - public Result getCurrentUserInfo() { - log.info("获取当前用户信息请求"); - UserInfoResponse response = authService.getCurrentUserInfo(); - return Result.success(response); - } - - @Operation(summary = "检查账号是否存在") - @GetMapping("/check-account") - public Result checkAccount( - @Parameter(description = "账号") @RequestParam String account) { - log.info("检查账号是否存在: {}", account); - boolean exists = authService.existsByAccount(account); - return Result.success(exists); - } - - @Operation(summary = "检查邮箱是否存在") - @GetMapping("/check-email") - public Result checkEmail( - @Parameter(description = "邮箱") @RequestParam String email) { - log.info("检查邮箱是否存在: {}", email); - boolean exists = authService.existsByEmail(email); - return Result.success(exists); - } - - @Operation(summary = "检查手机号是否存在") - @GetMapping("/check-phone") - public Result checkPhone( - @Parameter(description = "手机号") @RequestParam String phone) { - log.info("检查手机号是否存在: {}", phone); - boolean exists = authService.existsByPhone(phone); - return Result.success(exists); - } - - /** - * 重置密码(未登录场景) - * - * @param request 重置密码请求(手机号 + 新密码 + 验证码=123456) - * @return 重置密码响应 - */ - @Operation(summary = "重置密码(手机号+验证码)") - @PostMapping("/resetPassword") - public Result resetPassword(@Valid @RequestBody ResetPasswordRequest request) { - log.info("重置密码请求: phone={}", request.getPhone()); - ResetPasswordResponse response = authService.resetPassword(request); - return Result.success("重置密码成功", response); - } -} - diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/controller/CaptchaController.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/controller/CaptchaController.java deleted file mode 100644 index d868402..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/controller/CaptchaController.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.emotionmuseum.auth.controller; - -import com.emotionmuseum.common.result.Result; -import com.emotionmuseum.auth.response.CaptchaResponse; -import com.emotionmuseum.auth.response.SliderCaptchaResponse; -import com.emotionmuseum.auth.request.SliderCaptchaVerifyRequest; -import com.emotionmuseum.auth.service.CaptchaService; -import com.emotionmuseum.auth.service.SliderCaptchaService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.*; - -/** - * 验证码控制器 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Slf4j -@RestController -@RequestMapping("/captcha") -@RequiredArgsConstructor -@Tag(name = "验证码管理", description = "验证码生成和验证接口") -public class CaptchaController { - - private final CaptchaService captchaService; - private final SliderCaptchaService sliderCaptchaService; - - @Operation(summary = "生成验证码") - @GetMapping("/generate") - public Result generateCaptcha( - @Parameter(description = "验证码类型", example = "arithmetic") @RequestParam(defaultValue = "arithmetic") String type) { - log.info("生成验证码请求,类型: {}", type); - CaptchaResponse response = captchaService.generateCaptcha(type); - return Result.success(response); - } - - @Operation(summary = "验证验证码") - @PostMapping("/verify") - public Result verifyCaptcha( - @Parameter(description = "验证码ID") @RequestParam String captchaId, - @Parameter(description = "验证码") @RequestParam String captcha) { - log.info("验证验证码请求,ID: {}", captchaId); - boolean isValid = captchaService.verifyCaptcha(captchaId, captcha); - return Result.success(isValid); - } - - @Operation(summary = "生成滑块验证码") - @GetMapping("/slider/generate") - public Result generateSliderCaptcha() { - log.info("生成滑块验证码请求"); - SliderCaptchaResponse response = sliderCaptchaService.generateSliderCaptcha(); - return Result.success(response); - } - - @Operation(summary = "验证滑块验证码") - @PostMapping("/slider/verify") - public Result verifySliderCaptcha(@RequestBody SliderCaptchaVerifyRequest request) { - log.info("验证滑块验证码请求,ID: {}", request.getCaptchaId()); - boolean isValid = sliderCaptchaService.verifySliderCaptcha(request); - return Result.success(isValid); - } -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/controller/OAuthController.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/controller/OAuthController.java deleted file mode 100644 index c733632..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/controller/OAuthController.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.emotionmuseum.auth.controller; - -import com.emotionmuseum.common.result.Result; -import com.emotionmuseum.auth.dto.OAuthLoginRequest; -import com.emotionmuseum.auth.service.OAuthService; -import com.emotionmuseum.auth.vo.LoginResponse; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.*; - -import jakarta.validation.Valid; - -/** - * 第三方登录控制器 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Slf4j -@RestController -@RequestMapping("/oauth") -@RequiredArgsConstructor -@Tag(name = "第三方登录", description = "微信、QQ等第三方登录接口") -public class OAuthController { - - private final OAuthService oauthService; - - @Operation(summary = "获取第三方登录授权URL") - @GetMapping("/auth-url/{platform}") - public Result getAuthUrl( - @Parameter(description = "平台类型", example = "wechat") - @PathVariable String platform) { - log.info("获取第三方登录授权URL: {}", platform); - String authUrl = oauthService.getAuthUrl(platform); - return Result.success(authUrl); - } - - @Operation(summary = "第三方登录") - @PostMapping("/login") - public Result oauthLogin(@Valid @RequestBody OAuthLoginRequest request) { - log.info("第三方登录请求: {}", request.getPlatform()); - LoginResponse response = oauthService.oauthLogin(request); - return Result.success(response); - } - - @Operation(summary = "获取第三方用户信息") - @GetMapping("/user-info/{platform}") - public Result getOAuthUserInfo( - @Parameter(description = "平台类型") @PathVariable String platform, - @Parameter(description = "授权码") @RequestParam String code, - @Parameter(description = "状态码") @RequestParam(required = false) String state) { - log.info("获取第三方用户信息: {}", platform); - Object userInfo = oauthService.getOAuthUserInfo(platform, code, state); - return Result.success(userInfo); - } -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/dto/CaptchaResponse.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/dto/CaptchaResponse.java deleted file mode 100644 index 4f6b6b6..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/dto/CaptchaResponse.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.emotionmuseum.auth.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * 验证码响应 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@Schema(description = "验证码响应") -public class CaptchaResponse { - - @Schema(description = "验证码ID") - private String captchaId; - - @Schema(description = "验证码图片Base64") - private String captchaImage; - - @Schema(description = "验证码类型", example = "arithmetic") - private String captchaType; - - @Schema(description = "过期时间(秒)", example = "300") - private Long expireTime; -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/dto/LoginRequest.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/dto/LoginRequest.java deleted file mode 100644 index 7a3b2b4..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/dto/LoginRequest.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.emotionmuseum.auth.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotBlank; - -/** - * 用户登录请求 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Data -@Schema(description = "用户登录请求") -public class LoginRequest { - - @Schema(description = "账号(支持账号/邮箱/手机号)", example = "test_user") - @NotBlank(message = "账号不能为空") - private String account; - - @Schema(description = "密码", example = "123456") - @NotBlank(message = "密码不能为空") - private String password; - - @Schema(description = "验证码ID", example = "captcha_123") - @NotBlank(message = "验证码ID不能为空") - private String captchaId; - - @Schema(description = "验证码", example = "1234") - @NotBlank(message = "验证码不能为空") - private String captcha; - - @Schema(description = "记住我", example = "false") - private Boolean rememberMe = false; -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/dto/OAuthLoginRequest.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/dto/OAuthLoginRequest.java deleted file mode 100644 index 7b790d6..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/dto/OAuthLoginRequest.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.emotionmuseum.auth.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotBlank; - -/** - * 第三方登录请求 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Data -@Schema(description = "第三方登录请求") -public class OAuthLoginRequest { - - @Schema(description = "第三方平台类型", example = "wechat") - @NotBlank(message = "平台类型不能为空") - private String platform; - - @Schema(description = "授权码", example = "auth_code_123") - @NotBlank(message = "授权码不能为空") - private String code; - - @Schema(description = "状态码", example = "state_123") - private String state; - - @Schema(description = "验证码ID", example = "captcha_123") - @NotBlank(message = "验证码ID不能为空") - private String captchaId; - - @Schema(description = "验证码", example = "1234") - @NotBlank(message = "验证码不能为空") - private String captcha; -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/dto/RegisterRequest.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/dto/RegisterRequest.java deleted file mode 100644 index c4e3ce1..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/dto/RegisterRequest.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.emotionmuseum.auth.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.*; -import java.time.LocalDate; - -/** - * 用户注册请求 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Data -@Schema(description = "用户注册请求") -public class RegisterRequest { - - @Schema(description = "账号", example = "test_user") - @NotBlank(message = "账号不能为空") - @Size(min = 4, max = 20, message = "账号长度必须在4-20位之间") - @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "账号只能包含字母、数字和下划线") - private String account; - - @Schema(description = "密码", example = "123456") - @NotBlank(message = "密码不能为空") - @Size(min = 6, max = 20, message = "密码长度必须在6-20位之间") - private String password; - - @Schema(description = "确认密码", example = "123456") - @NotBlank(message = "确认密码不能为空") - private String confirmPassword; - - @Schema(description = "验证码ID", example = "captcha_123") - @NotBlank(message = "验证码ID不能为空") - private String captchaId; - - @Schema(description = "验证码", example = "1234") - @NotBlank(message = "验证码不能为空") - private String captcha; - - @Schema(description = "用户名", example = "测试用户") - @NotBlank(message = "用户名不能为空") - @Size(min = 2, max = 20, message = "用户名长度必须在2-20位之间") - private String username; - - @Schema(description = "邮箱", example = "test@example.com") - @NotBlank(message = "邮箱不能为空") - @Email(message = "邮箱格式不正确") - private String email; - - @Schema(description = "手机号", example = "13800138000") - @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") - private String phone; - - @Schema(description = "昵称", example = "小测试") - @NotBlank(message = "昵称不能为空") - @Size(min = 1, max = 20, message = "昵称长度必须在1-20位之间") - private String nickname; - - @Schema(description = "生日", example = "1990-01-01") - private LocalDate birthDate; - - @Schema(description = "所在地", example = "北京市") - @Size(max = 50, message = "所在地长度不能超过50位") - private String location; - - @Schema(description = "个人简介", example = "这是一个测试用户") - @Size(max = 200, message = "个人简介长度不能超过200位") - private String bio; - - /** - * 验证密码一致性 - */ - public boolean isPasswordMatch() { - return password != null && password.equals(confirmPassword); - } -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/dto/SliderCaptchaResponse.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/dto/SliderCaptchaResponse.java deleted file mode 100644 index b0bdc5c..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/dto/SliderCaptchaResponse.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.emotionmuseum.auth.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * 滑块验证码响应 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@Schema(description = "滑块验证码响应") -public class SliderCaptchaResponse { - - @Schema(description = "验证码ID") - private String captchaId; - - @Schema(description = "背景图片Base64") - private String backgroundImage; - - @Schema(description = "滑块图片Base64") - private String sliderImage; - - @Schema(description = "滑块X坐标") - private Integer sliderX; - - @Schema(description = "滑块Y坐标") - private Integer sliderY; - - @Schema(description = "过期时间(秒)") - private Long expireTime; -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/dto/SliderCaptchaVerifyRequest.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/dto/SliderCaptchaVerifyRequest.java deleted file mode 100644 index 8d20a4f..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/dto/SliderCaptchaVerifyRequest.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.emotionmuseum.auth.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; - -/** - * 滑块验证码验证请求 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Data -@Schema(description = "滑块验证码验证请求") -public class SliderCaptchaVerifyRequest { - - @Schema(description = "验证码ID") - @NotBlank(message = "验证码ID不能为空") - private String captchaId; - - @Schema(description = "滑块X坐标") - @NotNull(message = "滑块X坐标不能为空") - private Integer x; - - @Schema(description = "滑块Y坐标") - @NotNull(message = "滑块Y坐标不能为空") - private Integer y; - - @Schema(description = "滑动轨迹") - private String track; -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/entity/User.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/entity/User.java deleted file mode 100644 index c5b1bfe..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/entity/User.java +++ /dev/null @@ -1,160 +0,0 @@ -package com.emotionmuseum.auth.entity; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import com.emotionmuseum.common.entity.BaseEntity; -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; - -/** - * 用户实体 - * - * @author huazhongmin - * @since 2025-07-16 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName("user") -public class User extends BaseEntity { - - /** - * 账号 - */ - @TableField("account") - private String account; - - /** - * 密码 - */ - @TableField("password") - @JsonIgnore - private String password; - - /** - * 用户名 - */ - @TableField("username") - private String username; - - /** - * 邮箱 - */ - @TableField("email") - private String email; - - /** - * 手机号 - */ - @TableField("phone") - private String phone; - - /** - * 头像URL - */ - @TableField("avatar") - private String avatar; - - /** - * 昵称 - */ - @TableField("nickname") - private String nickname; - - /** - * 生日 - */ - @TableField("birth_date") - @JsonFormat(pattern = "yyyy-MM-dd") - private LocalDate birthDate; - - /** - * 所在地 - */ - @TableField("location") - private String location; - - /** - * 个人简介 - */ - @TableField("bio") - private String bio; - - /** - * 会员等级 - */ - @TableField("member_level") - private String memberLevel; - - /** - * 使用天数 - */ - @TableField("total_days") - private Integer totalDays; - - /** - * 自我感知 - */ - @TableField("self_awareness") - private BigDecimal selfAwareness; - - /** - * 情绪韧性 - */ - @TableField("emotional_resilience") - private BigDecimal emotionalResilience; - - /** - * 行动力 - */ - @TableField("action_power") - private BigDecimal actionPower; - - /** - * 共情力 - */ - @TableField("empathy") - private BigDecimal empathy; - - /** - * 生活热度 - */ - @TableField("life_enthusiasm") - private BigDecimal lifeEnthusiasm; - - /** - * 状态:0-禁用,1-正常 - */ - @TableField("status") - private Integer status; - - /** - * 是否已验证:0-未验证,1-已验证 - */ - @TableField("is_verified") - private Integer isVerified; - - /** - * 最后活跃时间 - */ - @TableField("last_active_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime lastActiveTime; - - /** - * 第三方登录平台 - */ - @TableField("oauth_platform") - private String oauthPlatform; - - /** - * 第三方登录ID - */ - @TableField("oauth_id") - private String oauthId; -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/mapper/UserMapper.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/mapper/UserMapper.java deleted file mode 100644 index dcca70a..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/mapper/UserMapper.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.emotionmuseum.auth.mapper; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.emotionmuseum.auth.entity.User; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; - -/** - * 用户Mapper接口 - * - * @author huazhongmin - * @since 2025-07-16 - */ -@Mapper -public interface UserMapper extends BaseMapper { - - /** - * 根据账号查询用户 - * - * @param account 账号 - * @return 用户信息 - */ - User selectByAccount(@Param("account") String account); - - /** - * 根据邮箱查询用户 - * - * @param email 邮箱 - * @return 用户信息 - */ - User selectByEmail(@Param("email") String email); - - /** - * 根据手机号查询用户 - * - * @param phone 手机号 - * @return 用户信息 - */ - User selectByPhone(@Param("phone") String phone); - - /** - * 根据第三方登录信息查询用户 - * - * @param platform 平台 - * @param oauthId 第三方ID - * @return 用户信息 - */ - User selectByOAuth(@Param("platform") String platform, @Param("oauthId") String oauthId); - - /** - * 更新最后活跃时间 - * - * @param userId 用户ID - */ - void updateLastActiveTime(@Param("userId") String userId); -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/request/LoginRequest.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/request/LoginRequest.java deleted file mode 100644 index 5ca47ab..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/request/LoginRequest.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.emotionmuseum.auth.request; - -import com.emotionmuseum.common.request.BaseRequest; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import jakarta.validation.constraints.NotBlank; - -/** - * 用户登录请求 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "用户登录请求") -public class LoginRequest extends BaseRequest { - - private static final long serialVersionUID = 1L; - - @Schema(description = "账号(支持账号/邮箱/手机号)", example = "test_user") - @NotBlank(message = "账号不能为空") - private String account; - - @Schema(description = "密码", example = "123456") - @NotBlank(message = "密码不能为空") - private String password; - - @Schema(description = "验证码ID", example = "captcha_123") - @NotBlank(message = "验证码ID不能为空") - private String captchaId; - - @Schema(description = "验证码", example = "1234") - @NotBlank(message = "验证码不能为空") - private String captcha; - - @Schema(description = "记住我", example = "false") - private Boolean rememberMe = false; -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/request/OAuthLoginRequest.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/request/OAuthLoginRequest.java deleted file mode 100644 index d6f188c..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/request/OAuthLoginRequest.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.emotionmuseum.auth.request; - -import com.emotionmuseum.common.request.BaseRequest; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import jakarta.validation.constraints.NotBlank; - -/** - * 第三方登录请求 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "第三方登录请求") -public class OAuthLoginRequest extends BaseRequest { - - private static final long serialVersionUID = 1L; - - @Schema(description = "第三方平台类型", example = "wechat") - @NotBlank(message = "平台类型不能为空") - private String platform; - - @Schema(description = "授权码", example = "auth_code_123") - @NotBlank(message = "授权码不能为空") - private String code; - - @Schema(description = "状态码", example = "state_123") - private String state; - - @Schema(description = "验证码ID", example = "captcha_123") - @NotBlank(message = "验证码ID不能为空") - private String captchaId; - - @Schema(description = "验证码", example = "1234") - @NotBlank(message = "验证码不能为空") - private String captcha; -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/request/RegisterRequest.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/request/RegisterRequest.java deleted file mode 100644 index 8df6f24..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/request/RegisterRequest.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.emotionmuseum.auth.request; - -import com.emotionmuseum.common.request.BaseRequest; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import jakarta.validation.constraints.*; -import java.time.LocalDate; - -/** - * 用户注册请求 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "用户注册请求") -public class RegisterRequest extends BaseRequest { - - private static final long serialVersionUID = 1L; - - @Schema(description = "账号", example = "test_user") - @NotBlank(message = "账号不能为空") - @Size(min = 4, max = 20, message = "账号长度必须在4-20位之间") - @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "账号只能包含字母、数字和下划线") - private String account; - - @Schema(description = "密码", example = "123456") - @NotBlank(message = "密码不能为空") - @Size(min = 6, max = 20, message = "密码长度必须在6-20位之间") - private String password; - - @Schema(description = "确认密码", example = "123456") - @NotBlank(message = "确认密码不能为空") - private String confirmPassword; - - @Schema(description = "验证码ID", example = "captcha_123") - @NotBlank(message = "验证码ID不能为空") - private String captchaId; - - @Schema(description = "验证码", example = "1234") - @NotBlank(message = "验证码不能为空") - private String captcha; - - @Schema(description = "用户名", example = "测试用户") - @NotBlank(message = "用户名不能为空") - @Size(min = 2, max = 20, message = "用户名长度必须在2-20位之间") - private String username; - - @Schema(description = "邮箱", example = "test@example.com") - @NotBlank(message = "邮箱不能为空") - @Email(message = "邮箱格式不正确") - private String email; - - @Schema(description = "手机号", example = "13800138000") - @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") - private String phone; - - @Schema(description = "昵称", example = "小测试") - @NotBlank(message = "昵称不能为空") - @Size(min = 1, max = 20, message = "昵称长度必须在1-20位之间") - private String nickname; - - @Schema(description = "生日", example = "1990-01-01") - private LocalDate birthDate; - - @Schema(description = "所在地", example = "北京市") - @Size(max = 50, message = "所在地长度不能超过50位") - private String location; - - @Schema(description = "个人简介", example = "这是一个测试用户") - @Size(max = 200, message = "个人简介长度不能超过200位") - private String bio; - - /** - * 验证密码一致性 - */ - public boolean isPasswordMatch() { - return password != null && password.equals(confirmPassword); - } -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/request/ResetPasswordRequest.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/request/ResetPasswordRequest.java deleted file mode 100644 index 3caf4de..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/request/ResetPasswordRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.emotionmuseum.auth.request; - -import com.emotionmuseum.common.request.BaseRequest; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Pattern; -import jakarta.validation.constraints.Size; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - * 重置密码请求 - * - *

用于未登录情况下通过手机号与验证码(本期固定为 123456)设置新密码。

- * - * @author huazhongmin - * @since 2025-10-26 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "重置密码请求") -public class ResetPasswordRequest extends BaseRequest { - - private static final long serialVersionUID = 1L; - - /** - * 手机号 - */ - @Schema(description = "手机号", example = "13800138000") - @NotBlank(message = "手机号不能为空") - @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") - private String phone; - - /** - * 新密码 - */ - @Schema(description = "新密码", example = "NewPass_123") - @NotBlank(message = "新密码不能为空") - @Size(min = 6, max = 20, message = "密码长度必须在6-20位之间") - private String newPassword; - - /** - * 验证码(本期为固定值 123456) - */ - @Schema(description = "验证码(固定为123456)", example = "123456") - @NotBlank(message = "验证码不能为空") - private String captcha; -} - diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/request/SliderCaptchaVerifyRequest.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/request/SliderCaptchaVerifyRequest.java deleted file mode 100644 index 158e41c..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/request/SliderCaptchaVerifyRequest.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.emotionmuseum.auth.request; - -import com.emotionmuseum.common.request.BaseRequest; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; - -/** - * 滑块验证码验证请求 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "滑块验证码验证请求") -public class SliderCaptchaVerifyRequest extends BaseRequest { - - private static final long serialVersionUID = 1L; - - @Schema(description = "验证码ID") - @NotBlank(message = "验证码ID不能为空") - private String captchaId; - - @Schema(description = "滑块X坐标") - @NotNull(message = "滑块X坐标不能为空") - private Integer x; - - @Schema(description = "滑块Y坐标") - @NotNull(message = "滑块Y坐标不能为空") - private Integer y; - - @Schema(description = "滑动轨迹") - private String track; -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/response/CaptchaResponse.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/response/CaptchaResponse.java deleted file mode 100644 index 0f0c984..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/response/CaptchaResponse.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.emotionmuseum.auth.response; - -import com.emotionmuseum.common.response.BaseResponse; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - -/** - * 验证码响应 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@EqualsAndHashCode(callSuper = true) -@Schema(description = "验证码响应") -public class CaptchaResponse extends BaseResponse { - - private static final long serialVersionUID = 1L; - - @Schema(description = "验证码ID") - private String captchaId; - - @Schema(description = "验证码图片Base64") - private String captchaImage; - - @Schema(description = "验证码类型", example = "arithmetic") - private String captchaType; - - @Schema(description = "过期时间(秒)", example = "300") - private Long expireTime; -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/response/LoginResponse.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/response/LoginResponse.java deleted file mode 100644 index 4cdd0b0..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/response/LoginResponse.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.emotionmuseum.auth.response; - -import com.emotionmuseum.common.response.BaseResponse; -import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.time.LocalDateTime; - -/** - * 登录响应 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "登录响应") -public class LoginResponse extends BaseResponse { - - private static final long serialVersionUID = 1L; - - @Schema(description = "访问Token") - private String accessToken; - - @Schema(description = "刷新Token") - private String refreshToken; - - @Schema(description = "Token类型", example = "Bearer") - private String tokenType = "Bearer"; - - @Schema(description = "Token过期时间(秒)", example = "86400") - private Long expiresIn; - - @Schema(description = "用户信息") - private UserInfoResponse userInfo; - - @Schema(description = "登录时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime loginTime; -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/response/ResetPasswordResponse.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/response/ResetPasswordResponse.java deleted file mode 100644 index 5e64ae5..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/response/ResetPasswordResponse.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.emotionmuseum.auth.response; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -/** - * 重置密码响应 - * - *

不返回敏感信息,仅提供结果标识与提示。

- * - * @author huazhongmin - * @since 2025-10-26 - */ -@Data -@Schema(description = "重置密码响应") -public class ResetPasswordResponse { - - /** 是否重置成功 */ - @Schema(description = "是否重置成功", example = "true") - private boolean success; - - /** 提示信息 */ - @Schema(description = "提示信息", example = "重置密码成功") - private String message; -} - diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/response/SliderCaptchaResponse.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/response/SliderCaptchaResponse.java deleted file mode 100644 index 2ab4b6c..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/response/SliderCaptchaResponse.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.emotionmuseum.auth.response; - -import com.emotionmuseum.common.response.BaseResponse; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - -/** - * 滑块验证码响应 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@EqualsAndHashCode(callSuper = true) -@Schema(description = "滑块验证码响应") -public class SliderCaptchaResponse extends BaseResponse { - - private static final long serialVersionUID = 1L; - - @Schema(description = "验证码ID") - private String captchaId; - - @Schema(description = "背景图片Base64") - private String backgroundImage; - - @Schema(description = "滑块图片Base64") - private String sliderImage; - - @Schema(description = "滑块X坐标") - private Integer sliderX; - - @Schema(description = "滑块Y坐标") - private Integer sliderY; - - @Schema(description = "过期时间(秒)") - private Long expireTime; -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/response/UserInfoResponse.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/response/UserInfoResponse.java deleted file mode 100644 index 8aa63f3..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/response/UserInfoResponse.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.emotionmuseum.auth.response; - -import com.emotionmuseum.common.response.BaseResponse; -import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; - -/** - * 用户信息响应 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "用户信息响应") -public class UserInfoResponse extends BaseResponse { - - private static final long serialVersionUID = 1L; - - @Schema(description = "用户ID") - private String id; - - @Schema(description = "账号") - private String account; - - @Schema(description = "用户名") - private String username; - - @Schema(description = "邮箱") - private String email; - - @Schema(description = "手机号") - private String phone; - - @Schema(description = "头像URL") - private String avatar; - - @Schema(description = "昵称") - private String nickname; - - @Schema(description = "生日") - @JsonFormat(pattern = "yyyy-MM-dd") - private LocalDate birthDate; - - @Schema(description = "所在地") - private String location; - - @Schema(description = "个人简介") - private String bio; - - @Schema(description = "会员等级") - private String memberLevel; - - @Schema(description = "使用天数") - private Integer totalDays; - - @Schema(description = "成长数据") - private GrowthStatsVO growthStats; - - @Schema(description = "状态") - private Integer status; - - @Schema(description = "是否已验证") - private Integer isVerified; - - @Schema(description = "创建时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; - - @Schema(description = "最后活跃时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime lastActiveTime; - - /** - * 成长数据VO - */ - @Data - @Schema(description = "成长数据") - public static class GrowthStatsVO { - - @Schema(description = "自我感知") - private BigDecimal selfAwareness; - - @Schema(description = "情绪韧性") - private BigDecimal emotionalResilience; - - @Schema(description = "行动力") - private BigDecimal actionPower; - - @Schema(description = "共情力") - private BigDecimal empathy; - - @Schema(description = "生活热度") - private BigDecimal lifeEnthusiasm; - } -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/security/JwtAuthenticationFilter.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/security/JwtAuthenticationFilter.java deleted file mode 100644 index 3cb8793..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/security/JwtAuthenticationFilter.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.emotionmuseum.auth.security; - -import cn.hutool.core.util.StrUtil; -import com.emotionmuseum.common.util.JwtUtil; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -/** - * JWT认证过滤器 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Slf4j -@Component -@RequiredArgsConstructor -public class JwtAuthenticationFilter extends OncePerRequestFilter { - - private final JwtUtil jwtUtil; - private final UserDetailsService userDetailsService; - private final RedisTemplate redisTemplate; - - private static final String TOKEN_PREFIX = "Bearer "; - private static final String HEADER_NAME = "Authorization"; - private static final String REDIS_TOKEN_KEY_PREFIX = "auth:token:"; - - @Override - protected void doFilterInternal(HttpServletRequest request, - HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { - - try { - String token = extractTokenFromRequest(request); - - if (StrUtil.isNotBlank(token) && SecurityContextHolder.getContext().getAuthentication() == null) { - // 验证token有效性 - if (jwtUtil.validateToken(token)) { - String userId = jwtUtil.getUserIdFromToken(token); - - // 检查Redis中是否存在该token(用于登出功能) - String redisKey = REDIS_TOKEN_KEY_PREFIX + userId; - String redisToken = (String) redisTemplate.opsForValue().get(redisKey); - - if (StrUtil.isNotBlank(redisToken) && redisToken.equals(token)) { - // 加载用户详情 - UserDetails userDetails = userDetailsService.loadUserByUsername(userId); - - // 创建认证对象 - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( - userDetails, null, userDetails.getAuthorities()); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - - // 设置到安全上下文 - SecurityContextHolder.getContext().setAuthentication(authentication); - - // 更新token在Redis中的过期时间 - redisTemplate.expire(redisKey, 24, TimeUnit.HOURS); - - log.debug("JWT认证成功,用户ID: {}", userId); - } else { - log.debug("Redis中未找到有效token,用户ID: {}", userId); - } - } else { - log.debug("JWT token无效"); - } - } - } catch (Exception e) { - log.error("JWT认证过程中发生错误: {}", e.getMessage()); - } - - filterChain.doFilter(request, response); - } - - /** - * 从请求中提取token - */ - private String extractTokenFromRequest(HttpServletRequest request) { - String bearerToken = request.getHeader(HEADER_NAME); - if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) { - return bearerToken.substring(TOKEN_PREFIX.length()); - } - return null; - } - - /** - * 判断是否跳过JWT认证 - */ - @Override - protected boolean shouldNotFilter(HttpServletRequest request) { - String path = request.getRequestURI(); - - // 跳过认证的路径 - return path.startsWith("/user/register") || - path.startsWith("/user/login") || - path.startsWith("/user/refresh") || - path.startsWith("/user/check/") || - path.startsWith("/captcha/") || - path.startsWith("/oauth/") || - path.startsWith("/actuator/") || - path.startsWith("/swagger-ui/") || - path.startsWith("/v3/api-docs") || - path.startsWith("/doc.html") || - path.equals("/error"); - } -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/security/UserDetailsServiceImpl.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/security/UserDetailsServiceImpl.java deleted file mode 100644 index ca9cca9..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/security/UserDetailsServiceImpl.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.emotionmuseum.auth.security; - -import com.emotionmuseum.auth.entity.User; -import com.emotionmuseum.auth.mapper.UserMapper; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; - -import java.util.Collection; -import java.util.Collections; - -/** - * Spring Security用户详情服务实现 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class UserDetailsServiceImpl implements UserDetailsService { - - private final UserMapper userMapper; - - @Override - public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException { - log.debug("加载用户详情,用户ID: {}", userId); - - User user = userMapper.selectById(userId); - if (user == null) { - log.warn("用户不存在,用户ID: {}", userId); - throw new UsernameNotFoundException("用户不存在: " + userId); - } - - if (user.getStatus() == 0) { - log.warn("用户已被禁用,用户ID: {}", userId); - throw new UsernameNotFoundException("用户已被禁用: " + userId); - } - - return new SecurityUser(user); - } - - /** - * Spring Security用户详情实现类 - */ - public static class SecurityUser implements UserDetails { - - private final User user; - - public SecurityUser(User user) { - this.user = user; - } - - @Override - public Collection getAuthorities() { - // 这里可以根据用户角色返回权限 - // 目前简单返回一个默认角色 - return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); - } - - @Override - public String getPassword() { - return user.getPassword(); - } - - @Override - public String getUsername() { - return user.getId(); - } - - /** - * 获取用户账号 - */ - public String getAccount() { - return user.getAccount(); - } - - /** - * 获取用户昵称 - */ - public String getNickname() { - return user.getNickname(); - } - - /** - * 获取用户实体 - */ - public User getUser() { - return user; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return user.getStatus() == 1; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return user.getStatus() == 1; - } - } -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/AuthService.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/AuthService.java deleted file mode 100644 index b440504..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/AuthService.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.emotionmuseum.auth.service; - -import com.emotionmuseum.auth.request.LoginRequest; -import com.emotionmuseum.auth.request.RegisterRequest; -import com.emotionmuseum.auth.request.ResetPasswordRequest; -import com.emotionmuseum.auth.response.LoginResponse; -import com.emotionmuseum.auth.response.ResetPasswordResponse; -import com.emotionmuseum.auth.response.UserInfoResponse; - -/** - * 认证服务接口 - * - *

- * 注意:所有新增接口需遵循项目接口规范与异常处理规范。 - *

- * - * @author huazhongmin - * @since 2025-07-16 - */ -public interface AuthService { - - /** - * 用户注册 - * - * @param request 注册请求 - * @return 用户信息响应 - */ - UserInfoResponse register(RegisterRequest request); - - /** - * 用户登录 - * - * @param request 登录请求 - * @return 登录响应 - */ - LoginResponse login(LoginRequest request); - - /** - * 刷新Token - * - * @param refreshToken 刷新Token - * @return 登录响应 - */ - LoginResponse refreshToken(String refreshToken); - - /** - * 用户登出 - * - * @param userId 用户ID - */ - void logout(String userId); - - /** - * 获取当前用户信息 - * - * @return 用户信息响应 - */ - UserInfoResponse getCurrentUserInfo(); - - /** - * 检查账号是否存在 - * - * @param account 账号 - * @return 是否存在 - */ - boolean existsByAccount(String account); - - /** - * 检查邮箱是否存在 - * - * @param email 邮箱 - * @return 是否存在 - */ - boolean existsByEmail(String email); - - /** - * 检查手机号是否存在 - * - * @param phone 手机号 - * @return 是否存在 - */ - boolean existsByPhone(String phone); - - /** - * 根据用户ID获取用户信息 - * - * @param userId 用户ID - * @return 用户信息响应 - */ - UserInfoResponse getUserInfo(String userId); - - /** - * 更新最后活跃时间 - * - * @param userId 用户ID - */ - void updateLastActiveTime(String userId); - - /** - * 重置密码(未登录场景) - * - *

- * 通过手机号与验证码(当前固定为123456)设置新密码。 - *

- * - * @param request 重置密码请求 - * @return 重置密码响应 - */ - ResetPasswordResponse resetPassword(ResetPasswordRequest request); -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/CaptchaService.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/CaptchaService.java deleted file mode 100644 index 993547e..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/CaptchaService.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.emotionmuseum.auth.service; - -import com.emotionmuseum.auth.response.CaptchaResponse; - -/** - * 验证码服务接口 - * - * @author huazhongmin - * @since 2025-07-15 - */ -public interface CaptchaService { - - /** - * 生成验证码 - * - * @param type 验证码类型 (arithmetic, chinese, gif, spec) - * @return 验证码响应 - */ - CaptchaResponse generateCaptcha(String type); - - /** - * 验证验证码 - * - * @param captchaId 验证码ID - * @param captcha 用户输入的验证码 - * @return 是否验证成功 - */ - boolean verifyCaptcha(String captchaId, String captcha); - - /** - * 删除验证码 - * - * @param captchaId 验证码ID - */ - void removeCaptcha(String captchaId); -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/OAuthService.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/OAuthService.java deleted file mode 100644 index 332e6a1..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/OAuthService.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.emotionmuseum.auth.service; - -import com.emotionmuseum.auth.dto.OAuthLoginRequest; -import com.emotionmuseum.auth.vo.LoginResponse; - -/** - * 第三方登录服务接口 - * - * @author huazhongmin - * @since 2025-07-15 - */ -public interface OAuthService { - - /** - * 获取第三方登录授权URL - * - * @param platform 平台类型 (wechat, qq, wechat-mp) - * @return 授权URL - */ - String getAuthUrl(String platform); - - /** - * 第三方登录 - * - * @param request 第三方登录请求 - * @return 登录响应 - */ - LoginResponse oauthLogin(OAuthLoginRequest request); - - /** - * 获取第三方用户信息 - * - * @param platform 平台类型 - * @param code 授权码 - * @param state 状态码 - * @return 用户信息 - */ - Object getOAuthUserInfo(String platform, String code, String state); -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/SliderCaptchaService.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/SliderCaptchaService.java deleted file mode 100644 index 02c1c54..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/SliderCaptchaService.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.emotionmuseum.auth.service; - -import com.emotionmuseum.auth.response.SliderCaptchaResponse; -import com.emotionmuseum.auth.request.SliderCaptchaVerifyRequest; - -/** - * 滑块验证码服务接口 - * - * @author huazhongmin - * @since 2025-07-15 - */ -public interface SliderCaptchaService { - - /** - * 生成滑块验证码 - * - * @return 滑块验证码响应 - */ - SliderCaptchaResponse generateSliderCaptcha(); - - /** - * 验证滑块验证码 - * - * @param request 验证请求 - * @return 是否验证成功 - */ - boolean verifySliderCaptcha(SliderCaptchaVerifyRequest request); - - /** - * 删除滑块验证码 - * - * @param captchaId 验证码ID - */ - void removeSliderCaptcha(String captchaId); -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/impl/AuthServiceImpl.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/impl/AuthServiceImpl.java deleted file mode 100644 index d0a2dca..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/impl/AuthServiceImpl.java +++ /dev/null @@ -1,347 +0,0 @@ -package com.emotionmuseum.auth.service.impl; - -import cn.hutool.core.util.StrUtil; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.emotionmuseum.common.result.ResultCode; -import com.emotionmuseum.common.util.JwtUtil; -import com.emotionmuseum.auth.request.LoginRequest; -import com.emotionmuseum.auth.request.RegisterRequest; -import com.emotionmuseum.auth.request.ResetPasswordRequest; -import com.emotionmuseum.auth.entity.User; -import com.emotionmuseum.auth.mapper.UserMapper; -import com.emotionmuseum.auth.service.AuthService; -import com.emotionmuseum.auth.service.CaptchaService; -import com.emotionmuseum.auth.response.LoginResponse; -import com.emotionmuseum.auth.response.ResetPasswordResponse; -import com.emotionmuseum.auth.response.UserInfoResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.BeanUtils; -import org.springframework.context.ApplicationContext; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.concurrent.TimeUnit; - -/** - * 认证服务实现 - * - * @author huazhongmin - * @since 2025-07-16 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class AuthServiceImpl extends ServiceImpl implements AuthService { - - private final ApplicationContext applicationContext; - private final CaptchaService captchaService; - private final JwtUtil jwtUtil; - private final RedisTemplate redisTemplate; - - private static final String REDIS_TOKEN_KEY_PREFIX = "auth:token:"; - - @Override - @Transactional(rollbackFor = Exception.class) - public UserInfoResponse register(RegisterRequest request) { - // 验证验证码 - if (!captchaService.verifyCaptcha(request.getCaptchaId(), request.getCaptcha())) { - throw new RuntimeException(ResultCode.CAPTCHA_ERROR.getMessage()); - } - - // 验证密码一致性 - if (!request.isPasswordMatch()) { - throw new RuntimeException(ResultCode.PARAM_VALIDATION_ERROR.getMessage() + ": 两次密码不一致"); - } - - // 检查账号是否存在 - if (existsByAccount(request.getAccount())) { - throw new RuntimeException(ResultCode.ACCOUNT_ALREADY_EXISTS.getMessage()); - } - - // 检查邮箱是否存在 - if (StrUtil.isNotBlank(request.getEmail()) && existsByEmail(request.getEmail())) { - throw new RuntimeException(ResultCode.EMAIL_ALREADY_EXISTS.getMessage()); - } - - // 检查手机号是否存在 - if (StrUtil.isNotBlank(request.getPhone()) && existsByPhone(request.getPhone())) { - throw new RuntimeException("手机号已存在"); - } - - // 创建用户 - User user = new User(); - BeanUtils.copyProperties(request, user); - - // 加密密码 - PasswordEncoder passwordEncoder = applicationContext.getBean(PasswordEncoder.class); - user.setPassword(passwordEncoder.encode(request.getPassword())); - - // 设置默认值 - user.setMemberLevel("free"); - user.setTotalDays(0); - user.setSelfAwareness(new BigDecimal("50.00")); - user.setEmotionalResilience(new BigDecimal("50.00")); - user.setActionPower(new BigDecimal("50.00")); - user.setEmpathy(new BigDecimal("50.00")); - user.setLifeEnthusiasm(new BigDecimal("50.00")); - user.setStatus(1); - user.setIsVerified(0); - user.setLastActiveTime(LocalDateTime.now()); - - // 保存用户 - save(user); - - log.info("用户注册成功: {}", user.getAccount()); - return convertToUserInfoResponse(user); - } - - @Override - public LoginResponse login(LoginRequest request) { - // 验证验证码 - if (!captchaService.verifyCaptcha(request.getCaptchaId(), request.getCaptcha())) { - throw new RuntimeException(ResultCode.CAPTCHA_ERROR.getMessage()); - } - - // 查找用户(支持账号/邮箱/手机号登录) - User user = findUserByAccount(request.getAccount()); - if (user == null) { - throw new RuntimeException(ResultCode.USER_NOT_FOUND.getMessage()); - } - - // 验证密码 - PasswordEncoder passwordEncoder = applicationContext.getBean(PasswordEncoder.class); - if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { - throw new RuntimeException(ResultCode.INVALID_CREDENTIALS.getMessage()); - } - - // 检查用户状态 - if (user.getStatus() == 0) { - throw new RuntimeException(ResultCode.USER_DISABLED.getMessage()); - } - - // 生成Token - String accessToken = jwtUtil.generateToken(user.getId(), user.getUsername()); - String refreshToken = jwtUtil.generateRefreshToken(user.getId(), user.getUsername()); - - // 将token存储到Redis中(用于登出和token管理) - String redisKey = REDIS_TOKEN_KEY_PREFIX + user.getId(); - redisTemplate.opsForValue().set(redisKey, accessToken, 24, TimeUnit.HOURS); - - // 更新最后活跃时间 - updateLastActiveTime(user.getId()); - - // 构建响应 - LoginResponse response = new LoginResponse(); - response.setAccessToken(accessToken); - response.setRefreshToken(refreshToken); - response.setExpiresIn(86400L); // 24小时 - response.setUserInfo(convertToUserInfoResponse(user)); - response.setLoginTime(LocalDateTime.now()); - - log.info("用户登录成功: {}", user.getAccount()); - return response; - } - - @Override - public LoginResponse refreshToken(String refreshToken) { - try { - // 验证刷新Token - if (!jwtUtil.validateToken(refreshToken)) { - throw new RuntimeException("刷新Token无效"); - } - - // 从刷新Token中获取用户信息 - String userId = jwtUtil.getUserIdFromToken(refreshToken); - User user = getById(userId); - if (user == null) { - throw new RuntimeException("用户不存在"); - } - - // 生成新的Token - String newAccessToken = jwtUtil.generateToken(user.getId(), user.getUsername()); - String newRefreshToken = jwtUtil.generateRefreshToken(user.getId(), user.getUsername()); - - // 更新Redis中的token - String redisKey = REDIS_TOKEN_KEY_PREFIX + user.getId(); - redisTemplate.opsForValue().set(redisKey, newAccessToken, 24, TimeUnit.HOURS); - - // 构建响应 - LoginResponse response = new LoginResponse(); - response.setAccessToken(newAccessToken); - response.setRefreshToken(newRefreshToken); - response.setExpiresIn(86400L); - response.setUserInfo(convertToUserInfoResponse(user)); - response.setLoginTime(LocalDateTime.now()); - - log.info("Token刷新成功: {}", user.getAccount()); - return response; - } catch (Exception e) { - log.error("Token刷新失败: {}", e.getMessage()); - throw new RuntimeException("Token刷新失败"); - } - } - - @Override - public void logout(String userId) { - try { - // 从Redis中删除token - String redisKey = REDIS_TOKEN_KEY_PREFIX + userId; - redisTemplate.delete(redisKey); - - log.info("用户登出成功: {}", userId); - } catch (Exception e) { - log.error("用户登出失败: {}", e.getMessage()); - throw new RuntimeException("登出失败"); - } - } - - @Override - public UserInfoResponse getCurrentUserInfo() { - // 从安全上下文获取当前用户ID - String userId = getCurrentUserId(); - if (StrUtil.isBlank(userId)) { - throw new RuntimeException("未登录"); - } - - User user = getById(userId); - if (user == null) { - throw new RuntimeException("用户不存在"); - } - - return convertToUserInfoResponse(user); - } - - @Override - public boolean existsByAccount(String account) { - return baseMapper.selectByAccount(account) != null; - } - - @Override - public boolean existsByEmail(String email) { - return baseMapper.selectByEmail(email) != null; - } - - @Override - public boolean existsByPhone(String phone) { - return baseMapper.selectByPhone(phone) != null; - } - - @Override - public UserInfoResponse getUserInfo(String userId) { - User user = getById(userId); - if (user == null) { - throw new RuntimeException("用户不存在"); - } - return convertToUserInfoResponse(user); - } - - /** - * 重置密码(未登录场景) - * - *

- * 校验验证码(当前固定为123456),按手机号查询用户,使用 PasswordEncoder(BCrypt) 加密新密码并更新。 - *

- * - * @param request 重置密码请求 - * @return 重置密码响应 - */ - @Override - public ResetPasswordResponse resetPassword(ResetPasswordRequest request) { - // 校验验证码(本期约定固定为 123456) - if (!"123456".equals(request.getCaptcha())) { - throw new RuntimeException(ResultCode.CAPTCHA_ERROR.getMessage()); - } - - // 按手机号查找用户 - User user = baseMapper.selectByPhone(request.getPhone()); - if (user == null) { - throw new RuntimeException(ResultCode.USER_NOT_FOUND.getMessage()); - } - - // 获取加密器并加密新密码 - PasswordEncoder passwordEncoder = applicationContext.getBean(PasswordEncoder.class); - String encoded = passwordEncoder.encode(request.getNewPassword()); - - // 仅更新密码与更新时间(遵循仅更新非空字段原则) - User toUpdate = new User(); - toUpdate.setId(user.getId()); - toUpdate.setPassword(encoded); - updateById(toUpdate); - - log.info("用户重置密码成功: phone={}", request.getPhone()); - ResetPasswordResponse resp = new ResetPasswordResponse(); - resp.setSuccess(true); - resp.setMessage("重置密码成功"); - return resp; - } - - @Override - public void updateLastActiveTime(String userId) { - baseMapper.updateLastActiveTime(userId); - } - - /** - * 根据账号查找用户(支持账号/邮箱/手机号) - */ - private User findUserByAccount(String account) { - // 先按账号查找 - User user = baseMapper.selectByAccount(account); - if (user != null) { - return user; - } - - // 按邮箱查找 - if (account.contains("@")) { - user = baseMapper.selectByEmail(account); - if (user != null) { - return user; - } - } - - // 按手机号查找 - if (account.matches("^1[3-9]\\d{9}$")) { - user = baseMapper.selectByPhone(account); - if (user != null) { - return user; - } - } - - return null; - } - - /** - * 转换为用户信息响应 - */ - private UserInfoResponse convertToUserInfoResponse(User user) { - UserInfoResponse response = new UserInfoResponse(); - BeanUtils.copyProperties(user, response); - - // 设置成长数据 - UserInfoResponse.GrowthStatsVO growthStats = new UserInfoResponse.GrowthStatsVO(); - growthStats.setSelfAwareness(user.getSelfAwareness()); - growthStats.setEmotionalResilience(user.getEmotionalResilience()); - growthStats.setActionPower(user.getActionPower()); - growthStats.setEmpathy(user.getEmpathy()); - growthStats.setLifeEnthusiasm(user.getLifeEnthusiasm()); - response.setGrowthStats(growthStats); - - return response; - } - - /** - * 获取当前用户ID - */ - private String getCurrentUserId() { - try { - return SecurityContextHolder.getContext().getAuthentication().getName(); - } catch (Exception e) { - return null; - } - } -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/impl/CaptchaServiceImpl.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/impl/CaptchaServiceImpl.java deleted file mode 100644 index 33a4c67..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/impl/CaptchaServiceImpl.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.emotionmuseum.auth.service.impl; - -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import com.emotionmuseum.auth.response.CaptchaResponse; -import com.emotionmuseum.auth.response.SliderCaptchaResponse; -import com.emotionmuseum.auth.request.SliderCaptchaVerifyRequest; -import com.emotionmuseum.auth.service.CaptchaService; -import com.emotionmuseum.auth.service.SliderCaptchaService; -import com.wf.captcha.base.Captcha; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.ApplicationContext; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; - -import java.util.concurrent.TimeUnit; - -/** - * 验证码服务实现 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class CaptchaServiceImpl implements CaptchaService { - - private final RedisTemplate redisTemplate; - private final ApplicationContext applicationContext; - - private static final String CAPTCHA_KEY_PREFIX = "captcha:"; - private static final long CAPTCHA_EXPIRE_TIME = 300; // 5分钟 - - @Override - public CaptchaResponse generateCaptcha(String type) { - try { - // 根据类型获取验证码Bean - String beanName = getBeanNameByType(type); - Captcha captcha = (Captcha) applicationContext.getBean(beanName); - - // 生成验证码 - String captchaId = IdUtil.simpleUUID(); - String captchaText = captcha.text(); - String captchaImage = captcha.toBase64(); - - // 存储到Redis - String redisKey = CAPTCHA_KEY_PREFIX + captchaId; - redisTemplate.opsForValue().set(redisKey, captchaText.toLowerCase(), CAPTCHA_EXPIRE_TIME, TimeUnit.SECONDS); - - log.debug("生成验证码成功,ID: {}, 内容: {}", captchaId, captchaText); - - return new CaptchaResponse(captchaId, captchaImage, type, CAPTCHA_EXPIRE_TIME); - } catch (Exception e) { - log.error("生成验证码失败: {}", e.getMessage()); - throw new RuntimeException("生成验证码失败"); - } - } - - @Override - public boolean verifyCaptcha(String captchaId, String captcha) { - if (StrUtil.isBlank(captchaId) || StrUtil.isBlank(captcha)) { - return false; - } - - try { - String redisKey = CAPTCHA_KEY_PREFIX + captchaId; - String storedCaptcha = (String) redisTemplate.opsForValue().get(redisKey); - - if (StrUtil.isBlank(storedCaptcha)) { - log.warn("验证码已过期或不存在,ID: {}", captchaId); - return false; - } - - // 验证码不区分大小写 - boolean isValid = storedCaptcha.equalsIgnoreCase(captcha.trim()); - - if (isValid) { - // 验证成功后删除验证码 - redisTemplate.delete(redisKey); - log.debug("验证码验证成功,ID: {}", captchaId); - } else { - log.warn("验证码验证失败,ID: {}, 期望: {}, 实际: {}", captchaId, storedCaptcha, captcha); - } - - return isValid; - } catch (Exception e) { - log.error("验证验证码失败: {}", e.getMessage()); - return false; - } - } - - @Override - public void removeCaptcha(String captchaId) { - if (StrUtil.isNotBlank(captchaId)) { - String redisKey = CAPTCHA_KEY_PREFIX + captchaId; - redisTemplate.delete(redisKey); - log.debug("删除验证码,ID: {}", captchaId); - } - } - - /** - * 根据类型获取Bean名称 - */ - private String getBeanNameByType(String type) { - String defaultType = StrUtil.blankToDefault(type, "spec"); - switch (defaultType) { - case "arithmetic": - return "arithmeticCaptcha"; - case "chinese": - return "chineseCaptcha"; - case "gif": - return "gifCaptcha"; - default: - return "specCaptcha"; - } - } -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/impl/OAuthServiceImpl.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/impl/OAuthServiceImpl.java deleted file mode 100644 index f99a6e0..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/impl/OAuthServiceImpl.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.emotionmuseum.auth.service.impl; - -import com.emotionmuseum.auth.dto.OAuthLoginRequest; -import com.emotionmuseum.auth.service.OAuthService; -import com.emotionmuseum.auth.vo.LoginResponse; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -/** - * 第三方登录服务实现类 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Slf4j -@Service -public class OAuthServiceImpl implements OAuthService { - - @Value("${oauth.wechat.client-id:}") - private String wechatClientId; - - @Value("${oauth.wechat.client-secret:}") - private String wechatClientSecret; - - @Value("${oauth.wechat.redirect-uri:}") - private String wechatRedirectUri; - - @Value("${oauth.qq.client-id:}") - private String qqClientId; - - @Value("${oauth.qq.client-secret:}") - private String qqClientSecret; - - @Value("${oauth.qq.redirect-uri:}") - private String qqRedirectUri; - - @Override - public String getAuthUrl(String platform) { - log.info("获取第三方登录授权URL, platform: {}", platform); - - switch (platform.toLowerCase()) { - case "wechat": - return buildWechatAuthUrl(); - case "qq": - return buildQQAuthUrl(); - default: - throw new IllegalArgumentException("不支持的第三方平台: " + platform); - } - } - - @Override - public LoginResponse oauthLogin(OAuthLoginRequest request) { - log.info("第三方登录, platform: {}, code: {}", request.getPlatform(), request.getCode()); - - // TODO: 实现第三方登录逻辑 - // 1. 根据code获取access_token - // 2. 根据access_token获取用户信息 - // 3. 查询或创建用户 - // 4. 生成JWT token - - throw new UnsupportedOperationException("第三方登录功能暂未实现"); - } - - @Override - public Object getOAuthUserInfo(String platform, String code, String state) { - log.info("获取第三方用户信息, platform: {}, code: {}, state: {}", platform, code, state); - - // TODO: 实现获取第三方用户信息逻辑 - - throw new UnsupportedOperationException("获取第三方用户信息功能暂未实现"); - } - - /** - * 构建微信授权URL - */ - private String buildWechatAuthUrl() { - if (wechatClientId.isEmpty()) { - throw new IllegalStateException("微信OAuth配置未完成"); - } - - // TODO: 构建微信授权URL - return "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + wechatClientId + - "&redirect_uri=" + wechatRedirectUri + - "&response_type=code&scope=snsapi_userinfo&state=wechat#wechat_redirect"; - } - - /** - * 构建QQ授权URL - */ - private String buildQQAuthUrl() { - if (qqClientId.isEmpty()) { - throw new IllegalStateException("QQ OAuth配置未完成"); - } - - // TODO: 构建QQ授权URL - return "https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=" + qqClientId + - "&redirect_uri=" + qqRedirectUri + - "&state=qq&scope=get_user_info"; - } -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/impl/SliderCaptchaServiceImpl.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/impl/SliderCaptchaServiceImpl.java deleted file mode 100644 index f634c99..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/service/impl/SliderCaptchaServiceImpl.java +++ /dev/null @@ -1,210 +0,0 @@ -package com.emotionmuseum.auth.service.impl; - -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import com.emotionmuseum.auth.response.SliderCaptchaResponse; -import com.emotionmuseum.auth.request.SliderCaptchaVerifyRequest; -import com.emotionmuseum.auth.service.SliderCaptchaService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; - -import javax.imageio.ImageIO; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Base64; -import java.util.Random; -import java.util.concurrent.TimeUnit; - -/** - * 滑块验证码服务实现 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class SliderCaptchaServiceImpl implements SliderCaptchaService { - - private final RedisTemplate redisTemplate; - - private static final String SLIDER_CAPTCHA_KEY_PREFIX = "slider_captcha:"; - private static final long SLIDER_CAPTCHA_EXPIRE_TIME = 300; // 5分钟 - private static final int BACKGROUND_WIDTH = 300; - private static final int BACKGROUND_HEIGHT = 150; - private static final int SLIDER_WIDTH = 60; - private static final int SLIDER_HEIGHT = 60; - private static final int TOLERANCE = 5; // 容错范围 - - @Override - public SliderCaptchaResponse generateSliderCaptcha() { - try { - String captchaId = IdUtil.simpleUUID(); - - // 生成随机位置 - Random random = new Random(); - int sliderX = random.nextInt(BACKGROUND_WIDTH - SLIDER_WIDTH - 50) + 50; - int sliderY = random.nextInt(BACKGROUND_HEIGHT - SLIDER_HEIGHT - 20) + 20; - - // 生成背景图片 - BufferedImage backgroundImage = generateBackgroundImage(sliderX, sliderY); - String backgroundBase64 = imageToBase64(backgroundImage); - - // 生成滑块图片 - BufferedImage sliderImage = generateSliderImage(); - String sliderBase64 = imageToBase64(sliderImage); - - // 存储到Redis - String redisKey = SLIDER_CAPTCHA_KEY_PREFIX + captchaId; - SliderCaptchaData data = new SliderCaptchaData(sliderX, sliderY); - redisTemplate.opsForValue().set(redisKey, data, SLIDER_CAPTCHA_EXPIRE_TIME, TimeUnit.SECONDS); - - log.debug("生成滑块验证码成功,ID: {}, 位置: ({}, {})", captchaId, sliderX, sliderY); - - return new SliderCaptchaResponse(captchaId, backgroundBase64, sliderBase64, 0, sliderY, SLIDER_CAPTCHA_EXPIRE_TIME); - } catch (Exception e) { - log.error("生成滑块验证码失败: {}", e.getMessage()); - throw new RuntimeException("生成滑块验证码失败"); - } - } - - @Override - public boolean verifySliderCaptcha(SliderCaptchaVerifyRequest request) { - if (StrUtil.isBlank(request.getCaptchaId()) || request.getX() == null) { - return false; - } - - try { - String redisKey = SLIDER_CAPTCHA_KEY_PREFIX + request.getCaptchaId(); - SliderCaptchaData data = (SliderCaptchaData) redisTemplate.opsForValue().get(redisKey); - - if (data == null) { - log.warn("滑块验证码已过期或不存在,ID: {}", request.getCaptchaId()); - return false; - } - - // 验证X坐标是否在容错范围内 - boolean isValid = Math.abs(data.getSliderX() - request.getX()) <= TOLERANCE; - - if (isValid) { - // 验证成功后删除验证码 - redisTemplate.delete(redisKey); - log.debug("滑块验证码验证成功,ID: {}", request.getCaptchaId()); - } else { - log.warn("滑块验证码验证失败,ID: {}, 期望X: {}, 实际X: {}", - request.getCaptchaId(), data.getSliderX(), request.getX()); - } - - return isValid; - } catch (Exception e) { - log.error("验证滑块验证码失败: {}", e.getMessage()); - return false; - } - } - - @Override - public void removeSliderCaptcha(String captchaId) { - if (StrUtil.isNotBlank(captchaId)) { - String redisKey = SLIDER_CAPTCHA_KEY_PREFIX + captchaId; - redisTemplate.delete(redisKey); - log.debug("删除滑块验证码,ID: {}", captchaId); - } - } - - /** - * 生成背景图片 - */ - private BufferedImage generateBackgroundImage(int sliderX, int sliderY) { - BufferedImage image = new BufferedImage(BACKGROUND_WIDTH, BACKGROUND_HEIGHT, BufferedImage.TYPE_INT_RGB); - Graphics2D g2d = image.createGraphics(); - - // 设置抗锯齿 - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - // 绘制渐变背景 - GradientPaint gradient = new GradientPaint(0, 0, new Color(135, 206, 250), - BACKGROUND_WIDTH, BACKGROUND_HEIGHT, new Color(70, 130, 180)); - g2d.setPaint(gradient); - g2d.fillRect(0, 0, BACKGROUND_WIDTH, BACKGROUND_HEIGHT); - - // 绘制一些装饰性图形 - Random random = new Random(); - g2d.setColor(new Color(255, 255, 255, 100)); - for (int i = 0; i < 20; i++) { - int x = random.nextInt(BACKGROUND_WIDTH); - int y = random.nextInt(BACKGROUND_HEIGHT); - int size = random.nextInt(20) + 5; - g2d.fillOval(x, y, size, size); - } - - // 绘制滑块缺口 - g2d.setColor(new Color(0, 0, 0, 150)); - g2d.fillRoundRect(sliderX, sliderY, SLIDER_WIDTH, SLIDER_HEIGHT, 10, 10); - - g2d.dispose(); - return image; - } - - /** - * 生成滑块图片 - */ - private BufferedImage generateSliderImage() { - BufferedImage image = new BufferedImage(SLIDER_WIDTH, SLIDER_HEIGHT, BufferedImage.TYPE_INT_ARGB); - Graphics2D g2d = image.createGraphics(); - - // 设置抗锯齿 - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - // 绘制滑块 - g2d.setColor(new Color(70, 130, 180)); - g2d.fillRoundRect(0, 0, SLIDER_WIDTH, SLIDER_HEIGHT, 10, 10); - - // 绘制边框 - g2d.setColor(new Color(255, 255, 255)); - g2d.setStroke(new BasicStroke(2)); - g2d.drawRoundRect(1, 1, SLIDER_WIDTH - 2, SLIDER_HEIGHT - 2, 10, 10); - - // 绘制箭头 - g2d.setColor(Color.WHITE); - int[] xPoints = {SLIDER_WIDTH/2 - 8, SLIDER_WIDTH/2 + 8, SLIDER_WIDTH/2}; - int[] yPoints = {SLIDER_HEIGHT/2, SLIDER_HEIGHT/2, SLIDER_HEIGHT/2 - 8}; - g2d.fillPolygon(xPoints, yPoints, 3); - - g2d.dispose(); - return image; - } - - /** - * 图片转Base64 - */ - private String imageToBase64(BufferedImage image) throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ImageIO.write(image, "PNG", baos); - byte[] bytes = baos.toByteArray(); - return "data:image/png;base64," + Base64.getEncoder().encodeToString(bytes); - } - - /** - * 滑块验证码数据 - */ - public static class SliderCaptchaData { - private int sliderX; - private int sliderY; - - public SliderCaptchaData() {} - - public SliderCaptchaData(int sliderX, int sliderY) { - this.sliderX = sliderX; - this.sliderY = sliderY; - } - - public int getSliderX() { return sliderX; } - public void setSliderX(int sliderX) { this.sliderX = sliderX; } - public int getSliderY() { return sliderY; } - public void setSliderY(int sliderY) { this.sliderY = sliderY; } - } -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/vo/LoginResponse.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/vo/LoginResponse.java deleted file mode 100644 index 6258e52..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/vo/LoginResponse.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.emotionmuseum.auth.vo; - -import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -/** - * 登录响应 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Data -@Schema(description = "登录响应") -public class LoginResponse { - - @Schema(description = "访问Token") - private String accessToken; - - @Schema(description = "刷新Token") - private String refreshToken; - - @Schema(description = "Token类型", example = "Bearer") - private String tokenType = "Bearer"; - - @Schema(description = "Token过期时间(秒)", example = "86400") - private Long expiresIn; - - @Schema(description = "用户信息") - private UserInfoResponse userInfo; - - @Schema(description = "登录时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime loginTime; -} diff --git a/backend/auth/server/src/main/java/com/emotionmuseum/auth/vo/UserInfoResponse.java b/backend/auth/server/src/main/java/com/emotionmuseum/auth/vo/UserInfoResponse.java deleted file mode 100644 index b73aa51..0000000 --- a/backend/auth/server/src/main/java/com/emotionmuseum/auth/vo/UserInfoResponse.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.emotionmuseum.auth.vo; - -import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; - -/** - * 用户信息响应 - * - * @author huazhongmin - * @since 2025-07-16 - */ -@Data -@Schema(description = "用户信息响应") -public class UserInfoResponse { - - @Schema(description = "用户ID") - private String id; - - @Schema(description = "账号") - private String account; - - @Schema(description = "用户名") - private String username; - - @Schema(description = "邮箱") - private String email; - - @Schema(description = "手机号") - private String phone; - - @Schema(description = "头像URL") - private String avatar; - - @Schema(description = "昵称") - private String nickname; - - @Schema(description = "生日") - @JsonFormat(pattern = "yyyy-MM-dd") - private LocalDate birthDate; - - @Schema(description = "所在地") - private String location; - - @Schema(description = "个人简介") - private String bio; - - @Schema(description = "会员等级") - private String memberLevel; - - @Schema(description = "使用天数") - private Integer totalDays; - - @Schema(description = "成长数据") - private GrowthStatsVO growthStats; - - @Schema(description = "状态") - private Integer status; - - @Schema(description = "是否已验证") - private Integer isVerified; - - @Schema(description = "创建时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; - - @Schema(description = "最后活跃时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime lastActiveTime; - - /** - * 成长数据VO - */ - @Data - @Schema(description = "成长数据") - public static class GrowthStatsVO { - - @Schema(description = "自我感知") - private BigDecimal selfAwareness; - - @Schema(description = "情绪韧性") - private BigDecimal emotionalResilience; - - @Schema(description = "行动力") - private BigDecimal actionPower; - - @Schema(description = "共情力") - private BigDecimal empathy; - - @Schema(description = "生活热度") - private BigDecimal lifeEnthusiasm; - } -} diff --git a/backend/auth/server/src/main/resources/application-local.yml b/backend/auth/server/src/main/resources/application-local.yml deleted file mode 100644 index 4c43ab2..0000000 --- a/backend/auth/server/src/main/resources/application-local.yml +++ /dev/null @@ -1,103 +0,0 @@ -server: - port: 19008 - -spring: - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true - username: root - password: 123456 - - # 连接池配置 - hikari: - minimum-idle: 5 - maximum-pool-size: 20 - idle-timeout: 300000 - connection-timeout: 20000 - max-lifetime: 1200000 - pool-name: EmotionAuthHikariCP - - # Redis配置 - data: - redis: - host: localhost - port: 6379 - password: - database: 0 - timeout: 5000ms - lettuce: - pool: - max-active: 20 - max-idle: 10 - min-idle: 5 - max-wait: 2000ms - - # 云服务配置 - cloud: - nacos: - discovery: - server-addr: localhost:8848 - username: nacos - password: Peanut2817*# - config: - server-addr: localhost:8848 - username: nacos - password: Peanut2817*# - -# MyBatis Plus配置 -mybatis-plus: - configuration: - map-underscore-to-camel-case: true - log-impl: org.apache.ibatis.logging.stdout.StdOutImpl - global-config: - db-config: - id-type: assign_uuid - logic-delete-field: deleted - logic-delete-value: 1 - logic-not-delete-value: 0 - -# 监控配置 -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus - endpoint: - health: - show-details: always - metrics: - export: - prometheus: - enabled: true - -# 日志配置 -logging: - level: - com.emotionmuseum: debug - com.baomidou.mybatisplus: debug - pattern: - console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%logger{50}] - %msg%n" - -# JWT配置 -jwt: - secret: emotion-museum-secret-key-2025 - expiration: 86400 - refresh-expiration: 604800 - -# 验证码配置 -captcha: - type: arithmetic - length: 4 - expire-time: 300 - -# OAuth配置 -oauth: - wechat: - client-id: - client-secret: - redirect-uri: - qq: - client-id: - client-secret: - redirect-uri: diff --git a/backend/auth/server/src/main/resources/application-prod.yml b/backend/auth/server/src/main/resources/application-prod.yml deleted file mode 100644 index 9698fdf..0000000 --- a/backend/auth/server/src/main/resources/application-prod.yml +++ /dev/null @@ -1,104 +0,0 @@ -server: - port: 19008 - -spring: - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${MYSQL_DATABASE}?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true - username: ${MYSQL_USERNAME} - password: EmotionMuseum2025*# - - # 连接池配置 - hikari: - minimum-idle: 10 - maximum-pool-size: 50 - idle-timeout: 300000 - connection-timeout: 20000 - max-lifetime: 1200000 - pool-name: EmotionAuthHikariCP - - # Redis配置 - data: - redis: - host: ${REDIS_HOST} - port: ${REDIS_PORT} - password: ${REDIS_PASSWORD} - database: ${REDIS_DATABASE} - timeout: 5000ms - lettuce: - pool: - max-active: 50 - max-idle: 20 - min-idle: 10 - max-wait: 2000ms - - # 云服务配置 - cloud: - nacos: - discovery: - server-addr: ${NACOS_SERVER_ADDR} - username: ${NACOS_USERNAME} - password: EmotionMuseum2025*# - config: - server-addr: ${NACOS_SERVER_ADDR} - username: ${NACOS_USERNAME} - password: EmotionMuseum2025*# - -# MyBatis Plus配置 -mybatis-plus: - configuration: - map-underscore-to-camel-case: true - log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl - global-config: - db-config: - id-type: assign_uuid - logic-delete-field: deleted - logic-delete-value: 1 - logic-not-delete-value: 0 - -# 监控配置 -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus - endpoint: - health: - show-details: when-authorized - metrics: - export: - prometheus: - enabled: true - -# 日志配置 -logging: - level: - com.emotionmuseum: info - com.baomidou.mybatisplus: warn - root: warn - pattern: - console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%logger{50}] - %msg%n" - -# JWT配置 -jwt: - secret: ${JWT_SECRET} - expiration: ${JWT_EXPIRATION:86400} - refresh-expiration: ${JWT_REFRESH_EXPIRATION:604800} - -# 验证码配置 -captcha: - type: arithmetic - length: 4 - expire-time: 300 - -# OAuth配置 -oauth: - wechat: - client-id: ${WECHAT_CLIENT_ID} - client-secret: ${WECHAT_CLIENT_SECRET} - redirect-uri: ${WECHAT_REDIRECT_URI} - qq: - client-id: ${QQ_CLIENT_ID} - client-secret: ${QQ_CLIENT_SECRET} - redirect-uri: ${QQ_REDIRECT_URI} diff --git a/backend/auth/server/src/main/resources/application-test.yml b/backend/auth/server/src/main/resources/application-test.yml deleted file mode 100644 index c49dafd..0000000 --- a/backend/auth/server/src/main/resources/application-test.yml +++ /dev/null @@ -1,103 +0,0 @@ -server: - port: 19008 - -spring: - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${MYSQL_DATABASE}?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true - username: ${MYSQL_USERNAME} - password: EmotionMuseum2025*# - - # 连接池配置 - hikari: - minimum-idle: 5 - maximum-pool-size: 20 - idle-timeout: 300000 - connection-timeout: 20000 - max-lifetime: 1200000 - pool-name: EmotionAuthHikariCP - - # Redis配置 - data: - redis: - host: ${REDIS_HOST} - port: ${REDIS_PORT} - password: ${REDIS_PASSWORD} - database: ${REDIS_DATABASE} - timeout: 5000ms - lettuce: - pool: - max-active: 20 - max-idle: 10 - min-idle: 5 - max-wait: 2000ms - - # 云服务配置 - cloud: - nacos: - discovery: - server-addr: ${NACOS_SERVER_ADDR} - username: ${NACOS_USERNAME} - password: EmotionMuseum2025*# - config: - server-addr: ${NACOS_SERVER_ADDR} - username: ${NACOS_USERNAME} - password: EmotionMuseum2025*# - -# MyBatis Plus配置 -mybatis-plus: - configuration: - map-underscore-to-camel-case: true - log-impl: org.apache.ibatis.logging.stdout.StdOutImpl - global-config: - db-config: - id-type: assign_uuid - logic-delete-field: deleted - logic-delete-value: 1 - logic-not-delete-value: 0 - -# 监控配置 -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus - endpoint: - health: - show-details: always - metrics: - export: - prometheus: - enabled: true - -# 日志配置 -logging: - level: - com.emotionmuseum: info - com.baomidou.mybatisplus: info - pattern: - console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%logger{50}] - %msg%n" - -# JWT配置 -jwt: - secret: ${JWT_SECRET:emotion-museum-secret-key-2025} - expiration: ${JWT_EXPIRATION:86400} - refresh-expiration: ${JWT_REFRESH_EXPIRATION:604800} - -# 验证码配置 -captcha: - type: arithmetic - length: 4 - expire-time: 300 - -# OAuth配置 -oauth: - wechat: - client-id: ${WECHAT_CLIENT_ID} - client-secret: ${WECHAT_CLIENT_SECRET} - redirect-uri: ${WECHAT_REDIRECT_URI} - qq: - client-id: ${QQ_CLIENT_ID} - client-secret: ${QQ_CLIENT_SECRET} - redirect-uri: ${QQ_REDIRECT_URI} diff --git a/backend/auth/server/src/main/resources/application.yml b/backend/auth/server/src/main/resources/application.yml deleted file mode 100644 index 7d49a01..0000000 --- a/backend/auth/server/src/main/resources/application.yml +++ /dev/null @@ -1,23 +0,0 @@ -spring: - application: - name: emotion-auth - profiles: - active: ${SPRING_PROFILES_ACTIVE:local} - main: - allow-bean-definition-overriding: true - cloud: - nacos: - config: - enabled: false - discovery: - enabled: true - server-addr: 101.200.208.45:8848 - username: nacos - password: Peanut2817*# - - - -logging: - file: - path: /data/logs/emotion-museum/auth - diff --git a/backend/auth/server/src/main/resources/mapper/UserMapper.xml b/backend/auth/server/src/main/resources/mapper/UserMapper.xml deleted file mode 100644 index 576448e..0000000 --- a/backend/auth/server/src/main/resources/mapper/UserMapper.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - UPDATE user - SET last_active_time = NOW(), update_time = NOW() - WHERE id = #{userId} AND is_deleted = 0 - - - diff --git a/backend/build-all.sh b/backend/build-all.sh deleted file mode 100644 index be256f2..0000000 --- a/backend/build-all.sh +++ /dev/null @@ -1,261 +0,0 @@ -#!/bin/bash - -# 情感博物馆 - Jenkins构建脚本 -# 作者: emotion-museum -# 日期: 2025-07-18 -# 用途: 在Jenkins服务器上构建所有微服务jar包 - -set -e - -# 配置变量 -PROFILE="${DEPLOY_ENV:-test}" -PROJECT_NAME="${PROJECT_NAME:-emotion-museum}" - -# Jenkins构建信息 -BUILD_NUMBER="${BUILD_NUMBER:-manual}" -JOB_NAME="${JOB_NAME:-local-build}" -BUILD_URL="${BUILD_URL:-}" - -# 颜色输出 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -# 日志函数 -log_info() { - echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -# 服务列表 -SERVICES=( - "emotion-gateway:19000" - "emotion-user:19001" - "emotion-ai:19002" - "emotion-record:19003" - "emotion-growth:19004" - "emotion-explore:19005" - "emotion-reward:19006" - "emotion-websocket:19007" - "emotion-auth:19008" - "emotion-stats:19009" -) - -# 检查构建环境 -check_build_environment() { - log_info "检查构建环境..." - - # 检查Java版本 - if command -v java &> /dev/null; then - local java_version=$(java -version 2>&1 | head -1 | cut -d'"' -f2) - log_info "Java版本: $java_version" - else - log_error "Java未安装" - exit 1 - fi - - # 检查Maven版本 - if command -v mvn &> /dev/null; then - local maven_version=$(mvn -version | head -1 | awk '{print $3}') - log_info "Maven版本: $maven_version" - else - log_error "Maven未安装" - exit 1 - fi - - # 检查是否在Jenkins环境中 - if [ -n "$JENKINS_HOME" ] || [ -n "$BUILD_NUMBER" ]; then - log_info "检测到Jenkins环境" - log_info "构建编号: $BUILD_NUMBER" - log_info "任务名称: $JOB_NAME" - else - log_info "本地构建环境" - fi - - log_success "构建环境检查通过" -} - -# 清理旧的构建产物 -clean_old_artifacts() { - log_info "清理旧的构建产物..." - - # 清理父项目 - mvn clean -q - - # 清理各个子模块 - for service_info in "${SERVICES[@]}"; do - service_name=$(echo $service_info | cut -d':' -f1) - if [ -d "$service_name" ]; then - log_info "清理模块: $service_name" - cd $service_name - mvn clean -q - cd .. - fi - done - - log_success "构建产物清理完成" -} - -# 构建所有服务 -build_all_services() { - log_info "开始构建所有微服务..." - - # 先构建父项目 - log_info "构建父项目..." - if mvn install -DskipTests -q; then - log_success "父项目构建成功" - else - log_error "父项目构建失败" - exit 1 - fi - - # 构建各个微服务 - local build_success=0 - local build_failed=0 - - for service_info in "${SERVICES[@]}"; do - service_name=$(echo $service_info | cut -d':' -f1) - log_info "构建服务: $service_name" - - if [ ! -d "$service_name" ]; then - log_warning "服务目录不存在: $service_name" - continue - fi - - cd $service_name - if mvn package -DskipTests -P${PROFILE} -q; then - # 检查jar包是否生成 - if [ -f "target/${service_name}-1.0.0.jar" ]; then - local jar_size=$(du -h "target/${service_name}-1.0.0.jar" | cut -f1) - log_success "✅ $service_name 构建成功 (大小: $jar_size)" - build_success=$((build_success + 1)) - else - log_error "❌ $service_name jar包未生成" - build_failed=$((build_failed + 1)) - fi - else - log_error "❌ $service_name 构建失败" - build_failed=$((build_failed + 1)) - fi - cd .. - done - - log_info "构建统计: 成功 $build_success, 失败 $build_failed" - - if [ $build_failed -eq 0 ]; then - log_success "所有服务构建成功" - return 0 - else - log_error "部分服务构建失败" - return 1 - fi -} - -# 生成构建报告 -generate_build_report() { - local total_time=$1 - - echo "" - echo "========================================" - echo " 构建完成报告" - echo "========================================" - echo "项目名称: $PROJECT_NAME" - echo "构建环境: $PROFILE" - echo "构建时间: $(date '+%Y-%m-%d %H:%M:%S')" - echo "总耗时: ${total_time}s" - if [ "$BUILD_NUMBER" != "manual" ]; then - echo "Jenkins构建: #$BUILD_NUMBER" - echo "Jenkins任务: $JOB_NAME" - [ -n "$BUILD_URL" ] && echo "构建链接: $BUILD_URL" - fi - echo "========================================" - - echo "" - echo "📦 构建产物详情:" - printf "%-20s %-10s %-10s %s\n" "服务名称" "状态" "大小" "路径" - echo "----------------------------------------" - - local total_size=0 - for service_info in "${SERVICES[@]}"; do - service_name=$(echo $service_info | cut -d':' -f1) - jar_file="${service_name}/target/${service_name}-1.0.0.jar" - - if [ -f "$jar_file" ]; then - jar_size=$(du -h "$jar_file" | cut -f1) - # 兼容macOS和Linux的文件大小获取 - if [[ "$OSTYPE" == "darwin"* ]]; then - jar_bytes=$(stat -f%z "$jar_file" 2>/dev/null || echo "0") - else - jar_bytes=$(stat -c%s "$jar_file" 2>/dev/null || echo "0") - fi - total_size=$((total_size + jar_bytes)) - printf "%-20s ${GREEN}%-10s${NC} %-10s %s\n" "$service_name" "✅ 成功" "$jar_size" "$jar_file" - else - printf "%-20s ${RED}%-10s${NC} %-10s %s\n" "$service_name" "❌ 失败" "N/A" "未生成" - fi - done - - echo "" - echo "📊 构建统计:" - echo " 总产物大小: $(echo $total_size | awk '{printf "%.1fMB", $1/1024/1024}')" - echo " 构建工作空间: $(pwd)" - echo "" - echo "========================================" - echo "🎉 构建任务完成!" -} - -# 主函数 -main() { - local start_time=$(date +%s) - - log_info "🔨 开始Jenkins构建任务..." - log_info "构建环境: $PROFILE" - log_info "项目名称: $PROJECT_NAME" - - # 检查构建环境 - check_build_environment - - # 清理旧产物 - clean_old_artifacts - - # 构建所有服务 - if build_all_services; then - log_success "所有服务构建成功" - build_result=0 - else - log_error "部分服务构建失败" - build_result=1 - fi - - # 计算总耗时 - local end_time=$(date +%s) - local total_time=$((end_time - start_time)) - - # 生成构建报告 - generate_build_report $total_time - - # 返回构建结果 - if [ $build_result -eq 0 ]; then - log_success "🎉 Jenkins构建任务完成!" - exit 0 - else - log_error "⚠️ 构建任务部分失败,请检查错误日志" - exit 1 - fi -} - -# 执行主函数 -main "$@" diff --git a/backend/common/pom.xml b/backend/common/pom.xml deleted file mode 100644 index c5c9cde..0000000 --- a/backend/common/pom.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - 4.0.0 - - - com.emotionmuseum - backend - 1.0.0 - - - common - common - 公共模块 - - - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.boot - spring-boot-starter-validation - - - - - com.baomidou - mybatis-plus-boot-starter - - - - - org.springframework.boot - spring-boot-starter-data-redis - - - - - org.projectlombok - lombok - true - - - - - cn.hutool - hutool-all - - - - com.alibaba.fastjson2 - fastjson2 - - - - - io.jsonwebtoken - jjwt-api - - - - io.jsonwebtoken - jjwt-impl - - - - io.jsonwebtoken - jjwt-jackson - - - - - com.github.xiaoymin - knife4j-openapi3-spring-boot-starter - - - diff --git a/backend/common/src/main/java/com/emotionmuseum/common/config/MybatisPlusConfig.java b/backend/common/src/main/java/com/emotionmuseum/common/config/MybatisPlusConfig.java deleted file mode 100644 index 9eae747..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/config/MybatisPlusConfig.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.emotionmuseum.common.config; - -import com.baomidou.mybatisplus.annotation.DbType; -import com.baomidou.mybatisplus.core.config.GlobalConfig; -import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; -import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; -import com.emotionmuseum.common.handler.EmotionMetaObjectHandler; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * MyBatis-Plus 配置类 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Configuration -public class MybatisPlusConfig { - - /** - * MyBatis-Plus 拦截器配置 - * 添加分页插件 - */ - @Bean - public MybatisPlusInterceptor mybatisPlusInterceptor() { - MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); - - // 分页插件 - PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); - paginationInnerInterceptor.setDbType(DbType.MYSQL); - paginationInnerInterceptor.setOverflow(false); - paginationInnerInterceptor.setMaxLimit(1000L); - interceptor.addInnerInterceptor(paginationInnerInterceptor); - - return interceptor; - } - - /** - * 全局配置 - */ - @Bean - public GlobalConfig globalConfig() { - GlobalConfig globalConfig = new GlobalConfig(); - - // 设置元数据处理器 - globalConfig.setMetaObjectHandler(new EmotionMetaObjectHandler()); - - // 设置逻辑删除配置 - GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig(); - dbConfig.setLogicDeleteField("is_deleted"); // 逻辑删除字段名 - dbConfig.setLogicDeleteValue("1"); // 删除值 - dbConfig.setLogicNotDeleteValue("0"); // 未删除值 - globalConfig.setDbConfig(dbConfig); - - return globalConfig; - } -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/config/RestTemplateConfig.java b/backend/common/src/main/java/com/emotionmuseum/common/config/RestTemplateConfig.java deleted file mode 100644 index ce0f4d0..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/config/RestTemplateConfig.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.emotionmuseum.common.config; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.client.ClientHttpRequestInterceptor; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.web.client.RestTemplate; - -import java.io.IOException; -import java.time.Duration; -import java.util.Collections; - -/** - * RestTemplate配置类 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Slf4j -@Configuration -public class RestTemplateConfig { - - /** - * 默认RestTemplate - */ - @Bean("restTemplate") - public RestTemplate restTemplate(RestTemplateBuilder builder) { - return builder - .setConnectTimeout(Duration.ofSeconds(30)) - .setReadTimeout(Duration.ofSeconds(60)) - .interceptors(Collections.singletonList(loggingInterceptor())) - .build(); - } - - /** - * 长连接RestTemplate(用于AI接口等耗时操作) - */ - @Bean("longRestTemplate") - public RestTemplate longRestTemplate(RestTemplateBuilder builder) { - return builder - .setConnectTimeout(Duration.ofSeconds(60)) - .setReadTimeout(Duration.ofSeconds(300)) - .interceptors(Collections.singletonList(loggingInterceptor())) - .build(); - } - - /** - * 快速RestTemplate(用于内部服务调用) - */ - @Bean("fastRestTemplate") - public RestTemplate fastRestTemplate(RestTemplateBuilder builder) { - return builder - .setConnectTimeout(Duration.ofSeconds(5)) - .setReadTimeout(Duration.ofSeconds(10)) - .interceptors(Collections.singletonList(loggingInterceptor())) - .build(); - } - - /** - * 请求日志拦截器 - */ - private ClientHttpRequestInterceptor loggingInterceptor() { - return (request, body, execution) -> { - long startTime = System.currentTimeMillis(); - - // 记录请求信息 - log.debug("HTTP Request: {} {}", request.getMethod(), request.getURI()); - if (body.length > 0 && body.length < 1000) { - log.debug("Request Body: {}", new String(body)); - } - - ClientHttpResponse response = null; - try { - response = execution.execute(request, body); - - // 记录响应信息 - long duration = System.currentTimeMillis() - startTime; - log.debug("HTTP Response: {} {} - {}ms", - response.getStatusCode().value(), - response.getStatusText(), - duration); - - return response; - } catch (IOException e) { - long duration = System.currentTimeMillis() - startTime; - log.error("HTTP Request failed: {} {} - {}ms, Error: {}", - request.getMethod(), - request.getURI(), - duration, - e.getMessage()); - throw e; - } - }; - } -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/config/SnowflakeConfig.java b/backend/common/src/main/java/com/emotionmuseum/common/config/SnowflakeConfig.java deleted file mode 100644 index 06fa35d..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/config/SnowflakeConfig.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.emotionmuseum.common.config; - -import com.emotionmuseum.common.util.SnowflakeIdGenerator; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.util.Enumeration; - -/** - * 雪花算法配置类 - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Slf4j -@Configuration -public class SnowflakeConfig { - - /** - * 机器ID配置,可通过配置文件指定 - */ - @Value("${snowflake.machine-id:#{null}}") - private Long configuredMachineId; - - /** - * 创建雪花算法ID生成器Bean - * - * @return SnowflakeIdGenerator实例 - */ - @Bean - public SnowflakeIdGenerator snowflakeIdGenerator() { - long machineId = getMachineId(); - log.info("雪花算法配置完成,使用机器ID: {}", machineId); - return new SnowflakeIdGenerator(machineId); - } - - /** - * 获取机器ID - * 优先级:配置文件 > 网络接口MAC地址 > 系统时间戳 - * - * @return 机器ID - */ - private long getMachineId() { - // 1. 优先使用配置文件中的机器ID - if (configuredMachineId != null) { - long machineId = configuredMachineId % 1024; // 确保在0-1023范围内 - log.info("使用配置文件中的机器ID: {} (原始值: {})", machineId, configuredMachineId); - return machineId; - } - - // 2. 尝试使用网络接口MAC地址生成机器ID - try { - long machineId = getMachineIdFromMac(); - log.info("使用MAC地址生成的机器ID: {}", machineId); - return machineId; - } catch (Exception e) { - log.warn("无法从MAC地址生成机器ID: {}", e.getMessage()); - } - - // 3. 使用系统时间戳作为后备方案 - long machineId = System.currentTimeMillis() % 1024; - log.info("使用系统时间戳生成的机器ID: {}", machineId); - return machineId; - } - - /** - * 从MAC地址生成机器ID - * - * @return 机器ID - * @throws Exception 获取MAC地址失败 - */ - private long getMachineIdFromMac() throws Exception { - // 获取本机所有网络接口 - Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); - - while (networkInterfaces.hasMoreElements()) { - NetworkInterface networkInterface = networkInterfaces.nextElement(); - - // 跳过回环接口和虚拟接口 - if (networkInterface.isLoopback() || networkInterface.isVirtual() || !networkInterface.isUp()) { - continue; - } - - byte[] mac = networkInterface.getHardwareAddress(); - if (mac != null && mac.length >= 6) { - // 使用MAC地址的后两个字节生成机器ID - long machineId = ((long) (mac[mac.length - 2] & 0xFF) << 8) - | (long) (mac[mac.length - 1] & 0xFF); - return machineId % 1024; // 确保在0-1023范围内 - } - } - - // 如果没有找到合适的网络接口,使用本机IP地址 - InetAddress localHost = InetAddress.getLocalHost(); - byte[] address = localHost.getAddress(); - if (address.length >= 4) { - // 使用IP地址的后两个字节生成机器ID - long machineId = ((long) (address[address.length - 2] & 0xFF) << 8) - | (long) (address[address.length - 1] & 0xFF); - return machineId % 1024; - } - - throw new RuntimeException("无法获取网络接口信息生成机器ID"); - } -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/config/WebMvcConfig.java b/backend/common/src/main/java/com/emotionmuseum/common/config/WebMvcConfig.java deleted file mode 100644 index 1dfa62a..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/config/WebMvcConfig.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.emotionmuseum.common.config; - -import com.emotionmuseum.common.interceptor.UserContextInterceptor; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -/** - * Web MVC 配置类 - * 注册拦截器和其他Web相关配置 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Configuration -@RequiredArgsConstructor -public class WebMvcConfig implements WebMvcConfigurer { - - private final UserContextInterceptor userContextInterceptor; - - /** - * 注册拦截器 - */ - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(userContextInterceptor) - .addPathPatterns("/**") // 拦截所有请求 - .excludePathPatterns( - // 排除静态资源 - "/static/**", - "/css/**", - "/js/**", - "/images/**", - "/favicon.ico", - - // 排除Swagger相关 - "/swagger-ui/**", - "/swagger-resources/**", - "/v2/api-docs", - "/v3/api-docs", - "/doc.html", - - // 排除健康检查 - "/actuator/**", - "/health", - - // 排除错误页面 - "/error" - ) - .order(1); // 设置拦截器执行顺序 - } -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/dto/PageQuery.java b/backend/common/src/main/java/com/emotionmuseum/common/dto/PageQuery.java deleted file mode 100644 index 70bc668..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/dto/PageQuery.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.emotionmuseum.common.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotNull; - -/** - * 分页查询基类 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Data -@Schema(description = "分页查询参数") -public class PageQuery { - - /** - * 页码 - */ - @Schema(description = "页码", example = "1") - @NotNull(message = "页码不能为空") - @Min(value = 1, message = "页码最小为1") - private Integer pageNum = 1; - - /** - * 每页大小 - */ - @Schema(description = "每页大小", example = "10") - @NotNull(message = "每页大小不能为空") - @Min(value = 1, message = "每页大小最小为1") - @Max(value = 100, message = "每页大小最大为100") - private Integer pageSize = 10; - - /** - * 排序字段 - */ - @Schema(description = "排序字段", example = "create_time") - private String orderBy; - - /** - * 排序方向 - */ - @Schema(description = "排序方向", example = "desc") - private String orderDirection = "desc"; - - /** - * 搜索关键词 - */ - @Schema(description = "搜索关键词") - private String keyword; -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/entity/BaseEntity.java b/backend/common/src/main/java/com/emotionmuseum/common/entity/BaseEntity.java deleted file mode 100644 index 03fa2da..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/entity/BaseEntity.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.emotionmuseum.common.entity; - -import com.baomidou.mybatisplus.annotation.*; -import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.Data; - -import java.io.Serializable; -import java.time.LocalDateTime; - -/** - * 基础实体类 - * 包含所有表的公共字段:create_by, create_time, update_by, update_time, is_deleted, - * remarks - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Data -public abstract class BaseEntity implements Serializable { - - private static final long serialVersionUID = 1L; - - /** - * 主键ID - 使用雪花算法生成的字符串ID - * 避免前端JavaScript精度丢失问题 - */ - @TableId(value = "id", type = IdType.INPUT) - private String id; - - /** - * 创建人ID - */ - @TableField(value = "create_by", fill = FieldFill.INSERT) - private String createBy; - - /** - * 创建时间 - */ - @TableField(value = "create_time", fill = FieldFill.INSERT) - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; - - /** - * 更新人ID - */ - @TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE) - private String updateBy; - - /** - * 更新时间 - */ - @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime updateTime; - - /** - * 是否删除:0-未删除,1-已删除 - */ - @TableLogic(value = "0", delval = "1") - @TableField(value = "is_deleted", fill = FieldFill.INSERT) - private Integer isDeleted; - - /** - * 备注 - */ - @TableField(value = "remarks") - private String remarks; -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/exception/AuthException.java b/backend/common/src/main/java/com/emotionmuseum/common/exception/AuthException.java deleted file mode 100644 index ab16a1c..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/exception/AuthException.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.emotionmuseum.common.exception; - -import com.emotionmuseum.common.result.ResultCode; -import lombok.Getter; - -/** - * 认证异常 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Getter -public class AuthException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - /** - * 错误码 - */ - private final Integer code; - - /** - * 错误消息 - */ - private final String message; - - public AuthException(String message) { - super(message); - this.code = ResultCode.UNAUTHORIZED.getCode(); - this.message = message; - } - - public AuthException(Integer code, String message) { - super(message); - this.code = code; - this.message = message; - } - - public AuthException(ResultCode resultCode) { - super(resultCode.getMessage()); - this.code = resultCode.getCode(); - this.message = resultCode.getMessage(); - } - - public AuthException(String message, Throwable cause) { - super(message, cause); - this.code = ResultCode.UNAUTHORIZED.getCode(); - this.message = message; - } - - public AuthException(Integer code, String message, Throwable cause) { - super(message, cause); - this.code = code; - this.message = message; - } - - public AuthException(ResultCode resultCode, Throwable cause) { - super(resultCode.getMessage(), cause); - this.code = resultCode.getCode(); - this.message = resultCode.getMessage(); - } -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/exception/BusinessException.java b/backend/common/src/main/java/com/emotionmuseum/common/exception/BusinessException.java deleted file mode 100644 index 1b18a8d..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/exception/BusinessException.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.emotionmuseum.common.exception; - -import com.emotionmuseum.common.result.ResultCode; -import lombok.Getter; - -/** - * 业务异常 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Getter -public class BusinessException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - /** - * 错误码 - */ - private final Integer code; - - /** - * 错误消息 - */ - private final String message; - - public BusinessException(String message) { - super(message); - this.code = ResultCode.BUSINESS_ERROR.getCode(); - this.message = message; - } - - public BusinessException(Integer code, String message) { - super(message); - this.code = code; - this.message = message; - } - - public BusinessException(ResultCode resultCode) { - super(resultCode.getMessage()); - this.code = resultCode.getCode(); - this.message = resultCode.getMessage(); - } - - public BusinessException(ResultCode resultCode, String message) { - super(message); - this.code = resultCode.getCode(); - this.message = message; - } - - public BusinessException(String message, Throwable cause) { - super(message, cause); - this.code = ResultCode.BUSINESS_ERROR.getCode(); - this.message = message; - } - - public BusinessException(Integer code, String message, Throwable cause) { - super(message, cause); - this.code = code; - this.message = message; - } - - public BusinessException(ResultCode resultCode, Throwable cause) { - super(resultCode.getMessage(), cause); - this.code = resultCode.getCode(); - this.message = resultCode.getMessage(); - } -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/exception/CaptchaException.java b/backend/common/src/main/java/com/emotionmuseum/common/exception/CaptchaException.java deleted file mode 100644 index 30770ae..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/exception/CaptchaException.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.emotionmuseum.common.exception; - -import com.emotionmuseum.common.result.ResultCode; -import lombok.Getter; - -/** - * 验证码异常 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Getter -public class CaptchaException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - /** - * 错误码 - */ - private final Integer code; - - /** - * 错误消息 - */ - private final String message; - - public CaptchaException(String message) { - super(message); - this.code = ResultCode.CAPTCHA_ERROR.getCode(); - this.message = message; - } - - public CaptchaException(Integer code, String message) { - super(message); - this.code = code; - this.message = message; - } - - public CaptchaException(ResultCode resultCode) { - super(resultCode.getMessage()); - this.code = resultCode.getCode(); - this.message = resultCode.getMessage(); - } - - public CaptchaException(String message, Throwable cause) { - super(message, cause); - this.code = ResultCode.CAPTCHA_ERROR.getCode(); - this.message = message; - } - - public CaptchaException(Integer code, String message, Throwable cause) { - super(message, cause); - this.code = code; - this.message = message; - } - - public CaptchaException(ResultCode resultCode, Throwable cause) { - super(resultCode.getMessage(), cause); - this.code = resultCode.getCode(); - this.message = resultCode.getMessage(); - } -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/exception/GlobalExceptionHandler.java b/backend/common/src/main/java/com/emotionmuseum/common/exception/GlobalExceptionHandler.java deleted file mode 100644 index 69ba2ed..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/exception/GlobalExceptionHandler.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.emotionmuseum.common.exception; - -import com.emotionmuseum.common.result.Result; -import com.emotionmuseum.common.result.ResultCode; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.validation.BindException; -import org.springframework.validation.FieldError; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.validation.ConstraintViolation; -import jakarta.validation.ConstraintViolationException; -import java.util.Set; - -/** - * 全局异常处理器 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Slf4j -@RestControllerAdvice -public class GlobalExceptionHandler { - - /** - * 处理认证异常 - */ - @ExceptionHandler(AuthException.class) - public Result handleAuthException(AuthException e, HttpServletRequest request) { - log.warn("认证异常: {} {} - {}", request.getMethod(), request.getRequestURI(), e.getMessage()); - return Result.error(e.getCode(), e.getMessage()); - } - - /** - * 处理令牌异常 - */ - @ExceptionHandler(TokenException.class) - public Result handleTokenException(TokenException e, HttpServletRequest request) { - log.warn("令牌异常: {} {} - {}", request.getMethod(), request.getRequestURI(), e.getMessage()); - return Result.error(e.getCode(), e.getMessage()); - } - - /** - * 处理验证码异常 - */ - @ExceptionHandler(CaptchaException.class) - public Result handleCaptchaException(CaptchaException e, HttpServletRequest request) { - log.warn("验证码异常: {} {} - {}", request.getMethod(), request.getRequestURI(), e.getMessage()); - return Result.error(e.getCode(), e.getMessage()); - } - - /** - * 处理业务异常 - */ - @ExceptionHandler(BusinessException.class) - public Result handleBusinessException(BusinessException e, HttpServletRequest request) { - log.warn("业务异常: {} {} - {}", request.getMethod(), request.getRequestURI(), e.getMessage()); - return Result.error(e.getCode(), e.getMessage()); - } - - /** - * 处理参数校验异常 - */ - @ExceptionHandler(MethodArgumentNotValidException.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) { - log.warn("参数校验失败: {} {}", request.getMethod(), request.getRequestURI(), e); - - StringBuilder message = new StringBuilder("参数校验失败: "); - for (FieldError error : e.getBindingResult().getFieldErrors()) { - message.append(error.getField()).append(" ").append(error.getDefaultMessage()).append("; "); - } - - return Result.error(ResultCode.PARAM_VALIDATION_ERROR.getCode(), message.toString()); - } - - /** - * 处理Bean校验异常 - */ - @ExceptionHandler(BindException.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public Result handleBindException(BindException e, HttpServletRequest request) { - log.warn("参数绑定失败: {} {}", request.getMethod(), request.getRequestURI(), e); - - StringBuilder message = new StringBuilder("参数绑定失败: "); - for (FieldError error : e.getBindingResult().getFieldErrors()) { - message.append(error.getField()).append(" ").append(error.getDefaultMessage()).append("; "); - } - - return Result.error(ResultCode.PARAM_VALIDATION_ERROR.getCode(), message.toString()); - } - - /** - * 处理约束校验异常 - */ - @ExceptionHandler(ConstraintViolationException.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public Result handleConstraintViolationException(ConstraintViolationException e, HttpServletRequest request) { - log.warn("约束校验失败: {} {}", request.getMethod(), request.getRequestURI(), e); - - StringBuilder message = new StringBuilder("约束校验失败: "); - Set> violations = e.getConstraintViolations(); - for (ConstraintViolation violation : violations) { - message.append(violation.getPropertyPath()).append(" ").append(violation.getMessage()).append("; "); - } - - return Result.error(ResultCode.PARAM_VALIDATION_ERROR.getCode(), message.toString()); - } - - /** - * 处理非法参数异常 - */ - @ExceptionHandler(IllegalArgumentException.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public Result handleIllegalArgumentException(IllegalArgumentException e, HttpServletRequest request) { - log.warn("非法参数: {} {}", request.getMethod(), request.getRequestURI(), e); - return Result.error(ResultCode.BAD_REQUEST.getCode(), "参数错误: " + e.getMessage()); - } - - /** - * 处理空指针异常 - */ - @ExceptionHandler(NullPointerException.class) - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public Result handleNullPointerException(NullPointerException e, HttpServletRequest request) { - log.error("空指针异常: {} {}", request.getMethod(), request.getRequestURI(), e); - return Result.error(ResultCode.INTERNAL_SERVER_ERROR.getCode(), "系统内部错误"); - } - - /** - * 处理运行时异常 - */ - @ExceptionHandler(RuntimeException.class) - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public Result handleRuntimeException(RuntimeException e, HttpServletRequest request) { - log.error("运行时异常: {} {}", request.getMethod(), request.getRequestURI(), e); - return Result.error(ResultCode.INTERNAL_SERVER_ERROR.getCode(), "系统运行异常: " + e.getMessage()); - } - - /** - * 处理所有其他异常 - */ - @ExceptionHandler(Exception.class) - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public Result handleException(Exception e, HttpServletRequest request) { - log.error("未知异常: {} {}", request.getMethod(), request.getRequestURI(), e); - return Result.error(ResultCode.INTERNAL_SERVER_ERROR.getCode(), "系统异常,请联系管理员"); - } -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/exception/TokenException.java b/backend/common/src/main/java/com/emotionmuseum/common/exception/TokenException.java deleted file mode 100644 index 3602016..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/exception/TokenException.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.emotionmuseum.common.exception; - -import com.emotionmuseum.common.result.ResultCode; -import lombok.Getter; - -/** - * Token异常 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Getter -public class TokenException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - /** - * 错误码 - */ - private final Integer code; - - /** - * 错误消息 - */ - private final String message; - - public TokenException(String message) { - super(message); - this.code = ResultCode.TOKEN_INVALID.getCode(); - this.message = message; - } - - public TokenException(Integer code, String message) { - super(message); - this.code = code; - this.message = message; - } - - public TokenException(ResultCode resultCode) { - super(resultCode.getMessage()); - this.code = resultCode.getCode(); - this.message = resultCode.getMessage(); - } - - public TokenException(String message, Throwable cause) { - super(message, cause); - this.code = ResultCode.TOKEN_INVALID.getCode(); - this.message = message; - } - - public TokenException(Integer code, String message, Throwable cause) { - super(message, cause); - this.code = code; - this.message = message; - } - - public TokenException(ResultCode resultCode, Throwable cause) { - super(resultCode.getMessage(), cause); - this.code = resultCode.getCode(); - this.message = resultCode.getMessage(); - } -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/handler/EmotionMetaObjectHandler.java b/backend/common/src/main/java/com/emotionmuseum/common/handler/EmotionMetaObjectHandler.java deleted file mode 100644 index 41be9d9..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/handler/EmotionMetaObjectHandler.java +++ /dev/null @@ -1,226 +0,0 @@ -package com.emotionmuseum.common.handler; - -import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; -import com.emotionmuseum.common.util.SnowflakeIdGenerator; -import lombok.extern.slf4j.Slf4j; -import org.apache.ibatis.reflection.MetaObject; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; - -/** - * MyBatis-Plus 自动填充处理器 - * 自动填充公共字段:id, create_by, create_time, update_by, update_time - * 支持雪花算法自动生成主键ID - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Slf4j -@Component -public class EmotionMetaObjectHandler implements MetaObjectHandler { - - /** - * 雪花算法ID生成器 - */ - @Autowired - private SnowflakeIdGenerator snowflakeIdGenerator; - - /** - * 插入时自动填充 - */ - @Override - public void insertFill(MetaObject metaObject) { - try { - LocalDateTime now = LocalDateTime.now(); - String currentUserId = getCurrentUserId(); - - // 填充主键ID(如果为空) - fillPrimaryKey(metaObject); - - // 填充创建时间 - this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, now); - - // 填充更新时间 - this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, now); - - // 填充创建人ID - if (currentUserId != null) { - this.strictInsertFill(metaObject, "createBy", String.class, currentUserId); - } - - // 填充更新人ID - if (currentUserId != null) { - this.strictInsertFill(metaObject, "updateBy", String.class, currentUserId); - } - - // 填充逻辑删除字段默认值 - this.strictInsertFill(metaObject, "isDeleted", Integer.class, 0); - - log.debug("插入时自动填充完成: createTime={}, updateTime={}, createBy={}, updateBy={}", - now, now, currentUserId, currentUserId); - - } catch (Exception e) { - // 自动填充失败不应该影响业务逻辑 - log.warn("插入时自动填充失败,但不影响业务逻辑: {}", e.getMessage()); - } - } - - /** - * 更新时自动填充 - */ - @Override - public void updateFill(MetaObject metaObject) { - try { - LocalDateTime now = LocalDateTime.now(); - String currentUserId = getCurrentUserId(); - - // 填充更新时间 - this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, now); - - // 填充更新人ID - if (currentUserId != null) { - this.strictUpdateFill(metaObject, "updateBy", String.class, currentUserId); - } - - log.debug("更新时自动填充完成: updateTime={}, updateBy={}", now, currentUserId); - - } catch (Exception e) { - // 自动填充失败不应该影响业务逻辑 - log.warn("更新时自动填充失败,但不影响业务逻辑: {}", e.getMessage()); - } - } - - /** - * 填充主键ID - * 如果主键ID为空,则使用雪花算法生成 - * - * @param metaObject 元对象 - */ - private void fillPrimaryKey(MetaObject metaObject) { - try { - // 检查是否有id字段 - if (metaObject.hasSetter("id")) { - Object idValue = metaObject.getValue("id"); - // 如果ID为空,则生成新的ID - if (idValue == null || (idValue instanceof String && ((String) idValue).isEmpty())) { - String newId = snowflakeIdGenerator.nextIdAsString(); - this.strictInsertFill(metaObject, "id", String.class, newId); - log.debug("自动生成主键ID: {}", newId); - } - } - } catch (Exception e) { - log.warn("主键ID自动填充失败,但不影响业务逻辑: {}", e.getMessage()); - } - } - - /** - * 获取当前用户ID - * 优先级: - * 1. 从ThreadLocal获取(如果有用户上下文) - * 2. 从Spring Security获取(如果有认证信息) - * 3. 返回系统默认值 - * - * @return 当前用户ID,如果获取失败返回null - */ - private String getCurrentUserId() { - try { - // 1. 尝试从ThreadLocal获取用户ID(如果有用户上下文工具类) - String userIdFromContext = getUserIdFromContext(); - if (userIdFromContext != null) { - return userIdFromContext; - } - - // 2. 尝试从Spring Security获取用户ID - String userIdFromSecurity = getUserIdFromSecurity(); - if (userIdFromSecurity != null) { - return userIdFromSecurity; - } - - // 3. 返回系统默认值(用于系统操作或未登录用户) - return "system"; - - } catch (Exception e) { - log.debug("获取当前用户ID失败: {}", e.getMessage()); - return "system"; - } - } - - /** - * 从用户上下文获取用户ID - * 这里可以集成自定义的用户上下文工具类 - * - * @return 用户ID或null - */ - private String getUserIdFromContext() { - try { - // TODO: 集成用户上下文工具类 - // 例如:return UserContextHolder.getCurrentUserId(); - - // 临时实现:从线程变量获取 - return UserContextHolder.getCurrentUserId(); - - } catch (Exception e) { - log.debug("从用户上下文获取用户ID失败: {}", e.getMessage()); - return null; - } - } - - /** - * 从Spring Security获取用户ID - * - * @return 用户ID或null - */ - private String getUserIdFromSecurity() { - try { - // TODO: 集成Spring Security - // Authentication authentication = - // SecurityContextHolder.getContext().getAuthentication(); - // if (authentication != null && authentication.isAuthenticated() - // && !"anonymousUser".equals(authentication.getPrincipal())) { - // UserDetails userDetails = (UserDetails) authentication.getPrincipal(); - // return userDetails.getUsername(); // 或者从UserDetails中获取用户ID - // } - return null; - - } catch (Exception e) { - log.debug("从Spring Security获取用户ID失败: {}", e.getMessage()); - return null; - } - } - - /** - * 用户上下文持有者 - * 用于在当前线程中存储用户信息 - */ - public static class UserContextHolder { - - private static final ThreadLocal USER_ID_HOLDER = new ThreadLocal<>(); - - /** - * 设置当前用户ID - * - * @param userId 用户ID - */ - public static void setCurrentUserId(String userId) { - USER_ID_HOLDER.set(userId); - } - - /** - * 获取当前用户ID - * - * @return 用户ID - */ - public static String getCurrentUserId() { - return USER_ID_HOLDER.get(); - } - - /** - * 清除当前用户ID - */ - public static void clear() { - USER_ID_HOLDER.remove(); - } - } -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/interceptor/UserContextInterceptor.java b/backend/common/src/main/java/com/emotionmuseum/common/interceptor/UserContextInterceptor.java deleted file mode 100644 index e4d810a..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/interceptor/UserContextInterceptor.java +++ /dev/null @@ -1,209 +0,0 @@ -package com.emotionmuseum.common.interceptor; - -import com.emotionmuseum.common.handler.EmotionMetaObjectHandler; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; -import org.springframework.web.servlet.HandlerInterceptor; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -/** - * 用户上下文拦截器 - * 自动从请求头中提取用户信息并设置到ThreadLocal中 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Slf4j -@Component -public class UserContextInterceptor implements HandlerInterceptor { - - /** - * 用户ID请求头名称 - */ - private static final String USER_ID_HEADER = "X-User-Id"; - - /** - * 用户名请求头名称 - */ - private static final String USERNAME_HEADER = "X-Username"; - - /** - * Authorization请求头名称 - */ - private static final String AUTHORIZATION_HEADER = "Authorization"; - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) - throws Exception { - try { - // 1. 优先从自定义请求头获取用户ID - String userId = request.getHeader(USER_ID_HEADER); - - // 2. 如果没有自定义请求头,尝试从其他方式获取 - if (!StringUtils.hasText(userId)) { - userId = extractUserIdFromRequest(request); - } - - // 3. 如果仍然没有用户ID,使用默认值 - if (!StringUtils.hasText(userId)) { - userId = generateGuestUserId(request); - } - - // 4. 设置到ThreadLocal中 - EmotionMetaObjectHandler.UserContextHolder.setCurrentUserId(userId); - - log.debug("设置用户上下文: userId={}, requestUri={}", userId, request.getRequestURI()); - - } catch (Exception e) { - // 设置用户上下文失败不应该影响请求处理 - log.warn("设置用户上下文失败,使用默认值: {}", e.getMessage()); - EmotionMetaObjectHandler.UserContextHolder.setCurrentUserId("system"); - } - - return true; - } - - @Override - public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) - throws Exception { - try { - // 清除ThreadLocal,避免内存泄漏 - EmotionMetaObjectHandler.UserContextHolder.clear(); - log.debug("清除用户上下文: requestUri={}", request.getRequestURI()); - } catch (Exception e) { - log.warn("清除用户上下文失败: {}", e.getMessage()); - } - } - - /** - * 从请求中提取用户ID - * - * @param request HTTP请求 - * @return 用户ID或null - */ - private String extractUserIdFromRequest(HttpServletRequest request) { - try { - // 1. 从Authorization头解析JWT token(如果有) - String authHeader = request.getHeader(AUTHORIZATION_HEADER); - if (StringUtils.hasText(authHeader) && authHeader.startsWith("Bearer ")) { - String token = authHeader.substring(7); - String userIdFromToken = extractUserIdFromToken(token); - if (StringUtils.hasText(userIdFromToken)) { - return userIdFromToken; - } - } - - // 2. 从用户名请求头获取 - String username = request.getHeader(USERNAME_HEADER); - if (StringUtils.hasText(username)) { - return username; - } - - // 3. 从请求参数获取 - String userIdParam = request.getParameter("userId"); - if (StringUtils.hasText(userIdParam)) { - return userIdParam; - } - - return null; - - } catch (Exception e) { - log.debug("从请求中提取用户ID失败: {}", e.getMessage()); - return null; - } - } - - /** - * 从JWT token中提取用户ID - * - * @param token JWT token - * @return 用户ID或null - */ - private String extractUserIdFromToken(String token) { - try { - // TODO: 实现JWT token解析逻辑 - // 这里可以集成JWT工具类来解析token - // 例如: - // Claims claims = JwtUtils.parseToken(token); - // return claims.getSubject(); - - log.debug("JWT token解析功能待实现"); - return null; - - } catch (Exception e) { - log.debug("解析JWT token失败: {}", e.getMessage()); - return null; - } - } - - /** - * 为访客用户生成临时用户ID - * - * @param request HTTP请求 - * @return 临时用户ID - */ - private String generateGuestUserId(HttpServletRequest request) { - try { - // 1. 尝试从Session获取 - String sessionId = request.getSession(false) != null ? request.getSession().getId() : null; - if (StringUtils.hasText(sessionId)) { - return "guest_session_" + sessionId; - } - - // 2. 基于IP和User-Agent生成 - String clientIp = getClientIpAddress(request); - String userAgent = request.getHeader("User-Agent"); - - if (StringUtils.hasText(clientIp)) { - String hash = String.valueOf((clientIp + userAgent).hashCode()); - return "guest_" + Math.abs(Integer.parseInt(hash)); - } - - // 3. 使用时间戳作为最后的备选方案 - return "guest_" + System.currentTimeMillis(); - - } catch (Exception e) { - log.debug("生成访客用户ID失败: {}", e.getMessage()); - return "guest_" + System.currentTimeMillis(); - } - } - - /** - * 获取客户端真实IP地址 - * - * @param request HTTP请求 - * @return 客户端IP地址 - */ - private String getClientIpAddress(HttpServletRequest request) { - try { - String xForwardedFor = request.getHeader("X-Forwarded-For"); - if (StringUtils.hasText(xForwardedFor) && !"unknown".equalsIgnoreCase(xForwardedFor)) { - return xForwardedFor.split(",")[0].trim(); - } - - String xRealIp = request.getHeader("X-Real-IP"); - if (StringUtils.hasText(xRealIp) && !"unknown".equalsIgnoreCase(xRealIp)) { - return xRealIp; - } - - String proxyClientIp = request.getHeader("Proxy-Client-IP"); - if (StringUtils.hasText(proxyClientIp) && !"unknown".equalsIgnoreCase(proxyClientIp)) { - return proxyClientIp; - } - - String wlProxyClientIp = request.getHeader("WL-Proxy-Client-IP"); - if (StringUtils.hasText(wlProxyClientIp) && !"unknown".equalsIgnoreCase(wlProxyClientIp)) { - return wlProxyClientIp; - } - - return request.getRemoteAddr(); - - } catch (Exception e) { - log.debug("获取客户端IP地址失败: {}", e.getMessage()); - return "unknown"; - } - } -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/request/BasePageRequest.java b/backend/common/src/main/java/com/emotionmuseum/common/request/BasePageRequest.java deleted file mode 100644 index f9f8392..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/request/BasePageRequest.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.emotionmuseum.common.request; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; - -/** - * 基础分页请求类 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "基础分页请求") -public abstract class BasePageRequest extends BaseRequest { - - private static final long serialVersionUID = 1L; - - /** - * 页码,从1开始 - */ - @Schema(description = "页码,从1开始", example = "1", minimum = "1") - @Min(value = 1, message = "页码必须大于0") - private Integer pageNum = 1; - - /** - * 每页大小 - */ - @Schema(description = "每页大小", example = "20", minimum = "1", maximum = "100") - @Min(value = 1, message = "每页大小必须大于0") - @Max(value = 100, message = "每页大小不能超过100") - private Integer pageSize = 20; - - /** - * 排序字段 - */ - @Schema(description = "排序字段", example = "create_time") - private String sortField; - - /** - * 排序方向 - */ - @Schema(description = "排序方向", example = "DESC", allowableValues = {"ASC", "DESC"}) - private String sortOrder = "DESC"; - - /** - * 搜索关键词 - */ - @Schema(description = "搜索关键词", example = "关键词") - private String keyword; -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/request/BaseRequest.java b/backend/common/src/main/java/com/emotionmuseum/common/request/BaseRequest.java deleted file mode 100644 index 51a80f7..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/request/BaseRequest.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.emotionmuseum.common.request; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotBlank; -import java.io.Serializable; - -/** - * 基础请求类 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@Schema(description = "基础请求") -public abstract class BaseRequest implements Serializable { - - private static final long serialVersionUID = 1L; - - /** - * 请求ID,用于链路追踪 - */ - @Schema(description = "请求ID", example = "req_123456789") - private String requestId; - - /** - * 客户端IP地址 - */ - @Schema(description = "客户端IP地址", example = "192.168.1.100") - private String clientIp; - - /** - * 用户代理信息 - */ - @Schema(description = "用户代理信息", example = "Mozilla/5.0...") - private String userAgent; - - /** - * 请求时间戳 - */ - @Schema(description = "请求时间戳", example = "1721808000000") - private Long timestamp; - - /** - * 设备类型 - */ - @Schema(description = "设备类型", example = "WEB", allowableValues = {"WEB", "MOBILE", "APP"}) - private String deviceType; - - /** - * 应用版本 - */ - @Schema(description = "应用版本", example = "1.0.0") - private String appVersion; - - public BaseRequest() { - this.timestamp = System.currentTimeMillis(); - } -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/response/BasePageResponse.java b/backend/common/src/main/java/com/emotionmuseum/common/response/BasePageResponse.java deleted file mode 100644 index 3081d82..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/response/BasePageResponse.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.emotionmuseum.common.response; - -import com.baomidou.mybatisplus.core.metadata.IPage; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.List; - -/** - * 基础分页响应类 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "基础分页响应") -public class BasePageResponse extends BaseResponse { - - private static final long serialVersionUID = 1L; - - /** - * 当前页码 - */ - @Schema(description = "当前页码", example = "1") - private Long current; - - /** - * 每页大小 - */ - @Schema(description = "每页大小", example = "20") - private Long size; - - /** - * 总记录数 - */ - @Schema(description = "总记录数", example = "100") - private Long total; - - /** - * 总页数 - */ - @Schema(description = "总页数", example = "5") - private Long pages; - - /** - * 数据列表 - */ - @Schema(description = "数据列表") - private List records; - - /** - * 是否有下一页 - */ - @Schema(description = "是否有下一页", example = "true") - private Boolean hasNext; - - /** - * 是否有上一页 - */ - @Schema(description = "是否有上一页", example = "false") - private Boolean hasPrevious; - - public BasePageResponse() { - super(); - } - - public BasePageResponse(IPage page) { - super(); - this.current = page.getCurrent(); - this.size = page.getSize(); - this.total = page.getTotal(); - this.pages = page.getPages(); - this.records = page.getRecords(); - this.hasNext = this.current != null && this.pages != null && this.current < this.pages; - this.hasPrevious = this.current != null && this.current > 1; - } - - /** - * 静态工厂方法 - */ - public static BasePageResponse of(IPage page) { - return new BasePageResponse<>(page); - } - - /** - * 静态工厂方法 - */ - public static BasePageResponse of(List records, Long current, Long size, Long total) { - BasePageResponse response = new BasePageResponse<>(); - response.setRecords(records); - response.setCurrent(current); - response.setSize(size); - response.setTotal(total); - response.setPages((total + size - 1) / size); - response.setHasNext(current < response.getPages()); - response.setHasPrevious(current > 1); - return response; - } -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/response/BaseResponse.java b/backend/common/src/main/java/com/emotionmuseum/common/response/BaseResponse.java deleted file mode 100644 index 002d14c..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/response/BaseResponse.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.emotionmuseum.common.response; - -import com.fasterxml.jackson.annotation.JsonInclude; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.io.Serializable; - -/** - * 基础响应类 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@JsonInclude(JsonInclude.Include.NON_NULL) -@Schema(description = "基础响应") -public abstract class BaseResponse implements Serializable { - - private static final long serialVersionUID = 1L; - - /** - * 响应时间戳 - */ - @Schema(description = "响应时间戳", example = "1721808000000") - private Long timestamp; - - /** - * 请求ID,用于链路追踪 - */ - @Schema(description = "请求ID", example = "req_123456789") - private String requestId; - - /** - * 服务器处理时间(毫秒) - */ - @Schema(description = "服务器处理时间(毫秒)", example = "150") - private Long processingTime; - - /** - * 服务节点标识 - */ - @Schema(description = "服务节点标识", example = "node-001") - private String serverNode; - - public BaseResponse() { - this.timestamp = System.currentTimeMillis(); - } - - /** - * 设置请求ID - */ - public BaseResponse requestId(String requestId) { - this.requestId = requestId; - return this; - } - - /** - * 设置处理时间 - */ - public BaseResponse processingTime(Long processingTime) { - this.processingTime = processingTime; - return this; - } - - /** - * 设置服务节点 - */ - public BaseResponse serverNode(String serverNode) { - this.serverNode = serverNode; - return this; - } -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/result/Result.java b/backend/common/src/main/java/com/emotionmuseum/common/result/Result.java deleted file mode 100644 index 38d78e5..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/result/Result.java +++ /dev/null @@ -1,186 +0,0 @@ -package com.emotionmuseum.common.result; - -import com.fasterxml.jackson.annotation.JsonInclude; -import lombok.Data; - -import java.io.Serializable; - -/** - * 统一响应结果 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Data -@JsonInclude(JsonInclude.Include.NON_NULL) -public class Result implements Serializable { - - private static final long serialVersionUID = 1L; - - /** - * 响应码 - */ - private Integer code; - - /** - * 响应消息 - */ - private String message; - - /** - * 响应数据 - */ - private T data; - - /** - * 时间戳 - */ - private Long timestamp; - - /** - * 请求ID - */ - private String requestId; - - public Result() { - this.timestamp = System.currentTimeMillis(); - } - - public Result(Integer code, String message) { - this(); - this.code = code; - this.message = message; - } - - public Result(Integer code, String message, T data) { - this(code, message); - this.data = data; - } - - /** - * 成功响应 - */ - public static Result success() { - return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage()); - } - - /** - * 成功响应(带数据) - */ - public static Result success(T data) { - return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data); - } - - /** - * 成功响应(自定义消息) - */ - public static Result success(String message, T data) { - return new Result<>(ResultCode.SUCCESS.getCode(), message, data); - } - - /** - * 失败响应 - */ - public static Result error() { - return new Result<>(ResultCode.INTERNAL_SERVER_ERROR.getCode(), ResultCode.INTERNAL_SERVER_ERROR.getMessage()); - } - - /** - * 失败响应(自定义消息) - */ - public static Result error(String message) { - return new Result<>(ResultCode.INTERNAL_SERVER_ERROR.getCode(), message); - } - - /** - * 失败响应(自定义码和消息) - */ - public static Result error(Integer code, String message) { - return new Result<>(code, message); - } - - /** - * 失败响应(结果码枚举) - */ - public static Result error(ResultCode resultCode) { - return new Result<>(resultCode.getCode(), resultCode.getMessage()); - } - - /** - * 失败响应(结果码枚举 + 数据) - */ - public static Result error(ResultCode resultCode, T data) { - return new Result<>(resultCode.getCode(), resultCode.getMessage(), data); - } - - /** - * 判断是否成功 - */ - public boolean isSuccess() { - return ResultCode.SUCCESS.getCode().equals(this.code); - } - - /** - * 设置请求ID - */ - public Result requestId(String requestId) { - this.requestId = requestId; - return this; - } - - /** - * 未授权响应 - */ - public static Result unauthorized() { - return new Result<>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage()); - } - - /** - * 未授权响应(自定义消息) - */ - public static Result unauthorized(String message) { - return new Result<>(ResultCode.UNAUTHORIZED.getCode(), message); - } - - /** - * 禁止访问响应 - */ - public static Result forbidden() { - return new Result<>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage()); - } - - /** - * 禁止访问响应(自定义消息) - */ - public static Result forbidden(String message) { - return new Result<>(ResultCode.FORBIDDEN.getCode(), message); - } - - /** - * 请求参数错误响应 - */ - public static Result badRequest() { - return new Result<>(ResultCode.BAD_REQUEST.getCode(), ResultCode.BAD_REQUEST.getMessage()); - } - - /** - * 请求参数错误响应(自定义消息) - */ - public static Result badRequest(String message) { - return new Result<>(ResultCode.BAD_REQUEST.getCode(), message); - } - - /** - * 资源不存在响应 - */ - public static Result notFound() { - return new Result<>(ResultCode.NOT_FOUND.getCode(), ResultCode.NOT_FOUND.getMessage()); - } - - /** - * 资源不存在响应(自定义消息) - */ - public static Result notFound(String message) { - return new Result<>(ResultCode.NOT_FOUND.getCode(), message); - } -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/result/ResultCode.java b/backend/common/src/main/java/com/emotionmuseum/common/result/ResultCode.java deleted file mode 100644 index d4c690e..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/result/ResultCode.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.emotionmuseum.common.result; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * 响应状态码枚举 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Getter -@AllArgsConstructor -public enum ResultCode { - - // ========== 通用状态码 ========== - SUCCESS(200, "操作成功"), - BAD_REQUEST(400, "请求参数错误"), - UNAUTHORIZED(401, "未授权"), - FORBIDDEN(403, "禁止访问"), - NOT_FOUND(404, "资源不存在"), - METHOD_NOT_ALLOWED(405, "请求方法不允许"), - INTERNAL_SERVER_ERROR(500, "服务器内部错误"), - SERVICE_UNAVAILABLE(503, "服务不可用"), - - // ========== 业务状态码 1000-1999 ========== - BUSINESS_ERROR(1000, "业务处理失败"), - PARAM_VALIDATION_ERROR(1001, "参数校验失败"), - DATA_NOT_FOUND(1002, "数据不存在"), - DATA_ALREADY_EXISTS(1003, "数据已存在"), - OPERATION_NOT_ALLOWED(1004, "操作不被允许"), - - // ========== 用户相关 2000-2099 ========== - USER_NOT_FOUND(2000, "用户不存在"), - USER_ALREADY_EXISTS(2001, "用户已存在"), - USER_DISABLED(2002, "用户已被禁用"), - USER_NOT_VERIFIED(2003, "用户未验证"), - INVALID_CREDENTIALS(2004, "用户名或密码错误"), - PASSWORD_TOO_WEAK(2005, "密码强度不够"), - PHONE_ALREADY_EXISTS(2006, "手机号已存在"), - EMAIL_ALREADY_EXISTS(2007, "邮箱已存在"), - ACCOUNT_ALREADY_EXISTS(2008, "账号已存在"), - - // ========== 认证相关 2100-2199 ========== - TOKEN_INVALID(2100, "Token无效"), - TOKEN_EXPIRED(2101, "Token已过期"), - TOKEN_MISSING(2102, "Token缺失"), - REFRESH_TOKEN_INVALID(2103, "刷新Token无效"), - LOGIN_REQUIRED(2104, "请先登录"), - PERMISSION_DENIED(2105, "权限不足"), - CAPTCHA_ERROR(2106, "验证码错误"), - CAPTCHA_EXPIRED(2107, "验证码已过期"), - - // ========== AI对话相关 2200-2299 ========== - AI_SERVICE_ERROR(2200, "AI服务异常"), - CONVERSATION_NOT_FOUND(2201, "对话不存在"), - MESSAGE_SEND_FAILED(2202, "消息发送失败"), - EMOTION_ANALYSIS_FAILED(2203, "情绪分析失败"), - AI_RESPONSE_TIMEOUT(2204, "AI响应超时"), - - // ========== 情绪记录相关 2300-2399 ========== - EMOTION_RECORD_NOT_FOUND(2300, "情绪记录不存在"), - EMOTION_TYPE_INVALID(2301, "情绪类型无效"), - EMOTION_INTENSITY_INVALID(2302, "情绪强度无效"), - EMOTION_DATE_INVALID(2303, "情绪日期无效"), - - // ========== 成长课题相关 2400-2499 ========== - TOPIC_NOT_FOUND(2400, "课题不存在"), - TOPIC_NOT_UNLOCKED(2401, "课题未解锁"), - TOPIC_ALREADY_COMPLETED(2402, "课题已完成"), - INTERACTION_NOT_FOUND(2403, "互动记录不存在"), - TOPIC_CATEGORY_INVALID(2404, "课题分类无效"), - - // ========== 地图探索相关 2500-2599 ========== - LOCATION_NOT_FOUND(2500, "地点不存在"), - LOCATION_ALREADY_EXISTS(2501, "地点已存在"), - COORDINATE_INVALID(2502, "坐标无效"), - POST_NOT_FOUND(2503, "帖子不存在"), - COMMENT_NOT_FOUND(2504, "评论不存在"), - - // ========== 成就奖励相关 2600-2699 ========== - ACHIEVEMENT_NOT_FOUND(2600, "成就不存在"), - ACHIEVEMENT_ALREADY_UNLOCKED(2601, "成就已解锁"), - REWARD_NOT_FOUND(2602, "奖励不存在"), - INSUFFICIENT_POINTS(2603, "积分不足"), - REWARD_ALREADY_CLAIMED(2604, "奖励已领取"), - - // ========== 统计分析相关 2700-2799 ========== - STATS_CALCULATION_ERROR(2700, "统计计算错误"), - REPORT_GENERATION_FAILED(2701, "报告生成失败"), - DATA_EXPORT_FAILED(2702, "数据导出失败"), - - // ========== 文件上传相关 2800-2899 ========== - FILE_UPLOAD_FAILED(2800, "文件上传失败"), - FILE_TYPE_NOT_SUPPORTED(2801, "文件类型不支持"), - FILE_SIZE_EXCEEDED(2802, "文件大小超限"), - FILE_NOT_FOUND(2803, "文件不存在"), - - // ========== 第三方服务相关 2900-2999 ========== - THIRD_PARTY_SERVICE_ERROR(2900, "第三方服务异常"), - SMS_SEND_FAILED(2901, "短信发送失败"), - EMAIL_SEND_FAILED(2902, "邮件发送失败"), - MAP_SERVICE_ERROR(2903, "地图服务异常"), - PAYMENT_SERVICE_ERROR(2904, "支付服务异常"), - - // ========== 系统相关 9000-9999 ========== - SYSTEM_MAINTENANCE(9000, "系统维护中"), - RATE_LIMIT_EXCEEDED(9001, "请求频率超限"), - DATABASE_ERROR(9002, "数据库异常"), - CACHE_ERROR(9003, "缓存异常"), - MQ_ERROR(9004, "消息队列异常"), - CONFIG_ERROR(9005, "配置错误"); - - /** - * 状态码 - */ - private final Integer code; - - /** - * 状态消息 - */ - private final String message; -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/util/HttpUtil.java b/backend/common/src/main/java/com/emotionmuseum/common/util/HttpUtil.java deleted file mode 100644 index 2e7c413..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/util/HttpUtil.java +++ /dev/null @@ -1,202 +0,0 @@ -package com.emotionmuseum.common.util; - -import cn.hutool.core.util.StrUtil; -import com.alibaba.fastjson2.JSON; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.http.*; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestTemplate; - -import java.util.Map; - -/** - * HTTP工具类 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Slf4j -@Component -public class HttpUtil { - - @Autowired - @Qualifier("restTemplate") - private RestTemplate restTemplate; - - @Autowired - @Qualifier("longRestTemplate") - private RestTemplate longRestTemplate; - - @Autowired - @Qualifier("fastRestTemplate") - private RestTemplate fastRestTemplate; - - /** - * GET请求 - */ - public T get(String url, Class responseType) { - return get(url, null, responseType); - } - - /** - * GET请求(带请求头) - */ - public T get(String url, HttpHeaders headers, Class responseType) { - try { - HttpEntity entity = new HttpEntity<>(headers); - ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, responseType); - return response.getBody(); - } catch (RestClientException e) { - log.error("GET请求失败: url={}, error={}", url, e.getMessage()); - throw new RuntimeException("HTTP GET请求失败: " + e.getMessage()); - } - } - - /** - * POST请求 - */ - public T post(String url, Object requestBody, Class responseType) { - return post(url, requestBody, null, responseType); - } - - /** - * POST请求(带请求头) - */ - public T post(String url, Object requestBody, HttpHeaders headers, Class responseType) { - try { - if (headers == null) { - headers = new HttpHeaders(); - } - headers.setContentType(MediaType.APPLICATION_JSON); - - String jsonBody = requestBody instanceof String ? - (String) requestBody : JSON.toJSONString(requestBody); - - HttpEntity entity = new HttpEntity<>(jsonBody, headers); - ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, entity, responseType); - return response.getBody(); - } catch (RestClientException e) { - log.error("POST请求失败: url={}, body={}, error={}", url, requestBody, e.getMessage()); - throw new RuntimeException("HTTP POST请求失败: " + e.getMessage()); - } - } - - /** - * PUT请求 - */ - public T put(String url, Object requestBody, Class responseType) { - return put(url, requestBody, null, responseType); - } - - /** - * PUT请求(带请求头) - */ - public T put(String url, Object requestBody, HttpHeaders headers, Class responseType) { - try { - if (headers == null) { - headers = new HttpHeaders(); - } - headers.setContentType(MediaType.APPLICATION_JSON); - - String jsonBody = requestBody instanceof String ? - (String) requestBody : JSON.toJSONString(requestBody); - - HttpEntity entity = new HttpEntity<>(jsonBody, headers); - ResponseEntity response = restTemplate.exchange(url, HttpMethod.PUT, entity, responseType); - return response.getBody(); - } catch (RestClientException e) { - log.error("PUT请求失败: url={}, body={}, error={}", url, requestBody, e.getMessage()); - throw new RuntimeException("HTTP PUT请求失败: " + e.getMessage()); - } - } - - /** - * DELETE请求 - */ - public T delete(String url, Class responseType) { - return delete(url, null, responseType); - } - - /** - * DELETE请求(带请求头) - */ - public T delete(String url, HttpHeaders headers, Class responseType) { - try { - HttpEntity entity = new HttpEntity<>(headers); - ResponseEntity response = restTemplate.exchange(url, HttpMethod.DELETE, entity, responseType); - return response.getBody(); - } catch (RestClientException e) { - log.error("DELETE请求失败: url={}, error={}", url, e.getMessage()); - throw new RuntimeException("HTTP DELETE请求失败: " + e.getMessage()); - } - } - - /** - * 长连接POST请求(用于AI接口) - */ - public T longPost(String url, Object requestBody, HttpHeaders headers, Class responseType) { - try { - if (headers == null) { - headers = new HttpHeaders(); - } - headers.setContentType(MediaType.APPLICATION_JSON); - - String jsonBody = requestBody instanceof String ? - (String) requestBody : JSON.toJSONString(requestBody); - - HttpEntity entity = new HttpEntity<>(jsonBody, headers); - ResponseEntity response = longRestTemplate.exchange(url, HttpMethod.POST, entity, responseType); - return response.getBody(); - } catch (RestClientException e) { - log.error("长连接POST请求失败: url={}, body={}, error={}", url, requestBody, e.getMessage()); - throw new RuntimeException("HTTP长连接POST请求失败: " + e.getMessage()); - } - } - - /** - * 快速POST请求(用于内部服务调用) - */ - public T fastPost(String url, Object requestBody, HttpHeaders headers, Class responseType) { - try { - if (headers == null) { - headers = new HttpHeaders(); - } - headers.setContentType(MediaType.APPLICATION_JSON); - - String jsonBody = requestBody instanceof String ? - (String) requestBody : JSON.toJSONString(requestBody); - - HttpEntity entity = new HttpEntity<>(jsonBody, headers); - ResponseEntity response = fastRestTemplate.exchange(url, HttpMethod.POST, entity, responseType); - return response.getBody(); - } catch (RestClientException e) { - log.error("快速POST请求失败: url={}, body={}, error={}", url, requestBody, e.getMessage()); - throw new RuntimeException("HTTP快速POST请求失败: " + e.getMessage()); - } - } - - /** - * 创建带Authorization的请求头 - */ - public static HttpHeaders createAuthHeaders(String token) { - HttpHeaders headers = new HttpHeaders(); - if (StrUtil.isNotBlank(token)) { - headers.set("Authorization", token.startsWith("Bearer ") ? token : "Bearer " + token); - } - return headers; - } - - /** - * 创建带自定义请求头的HttpHeaders - */ - public static HttpHeaders createHeaders(Map headerMap) { - HttpHeaders headers = new HttpHeaders(); - if (headerMap != null && !headerMap.isEmpty()) { - headerMap.forEach(headers::set); - } - return headers; - } -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/util/JwtUtil.java b/backend/common/src/main/java/com/emotionmuseum/common/util/JwtUtil.java deleted file mode 100644 index 5431870..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/util/JwtUtil.java +++ /dev/null @@ -1,217 +0,0 @@ -package com.emotionmuseum.common.util; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.StrUtil; -import io.jsonwebtoken.*; -import io.jsonwebtoken.security.Keys; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import javax.crypto.SecretKey; -import java.util.Date; -import java.util.Map; - -/** - * JWT工具类 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Slf4j -@Component -public class JwtUtil { - - /** - * JWT密钥 - */ - @Value("${jwt.secret:emotion-museum-secret-key-2025}") - private String secret; - - /** - * JWT过期时间(秒) - */ - @Value("${jwt.expiration:86400}") - private Long expiration; - - /** - * 刷新Token过期时间(秒) - */ - @Value("${jwt.refresh-expiration:604800}") - private Long refreshExpiration; - - /** - * 获取密钥 - */ - private SecretKey getSecretKey() { - return Keys.hmacShaKeyFor(secret.getBytes()); - } - - /** - * 生成Token - * - * @param userId 用户ID - * @param username 用户名 - * @return Token - */ - public String generateToken(String userId, String username) { - return generateToken(userId, username, expiration); - } - - /** - * 生成刷新Token - * - * @param userId 用户ID - * @param username 用户名 - * @return 刷新Token - */ - public String generateRefreshToken(String userId, String username) { - return generateToken(userId, username, refreshExpiration); - } - - /** - * 生成Token - * - * @param userId 用户ID - * @param username 用户名 - * @param expiration 过期时间(秒) - * @return Token - */ - private String generateToken(String userId, String username, Long expiration) { - Date now = new Date(); - Date expiryDate = new Date(now.getTime() + expiration * 1000); - - return Jwts.builder() - .setSubject(userId) - .claim("username", username) - .setIssuedAt(now) - .setExpiration(expiryDate) - .signWith(getSecretKey(), SignatureAlgorithm.HS512) - .compact(); - } - - /** - * 从Token中获取用户ID - * - * @param token Token - * @return 用户ID - */ - public String getUserIdFromToken(String token) { - Claims claims = getClaimsFromToken(token); - return claims != null ? claims.getSubject() : null; - } - - /** - * 从Token中获取用户名 - * - * @param token Token - * @return 用户名 - */ - public String getUsernameFromToken(String token) { - Claims claims = getClaimsFromToken(token); - return claims != null ? claims.get("username", String.class) : null; - } - - /** - * 从Token中获取过期时间 - * - * @param token Token - * @return 过期时间 - */ - public Date getExpirationDateFromToken(String token) { - Claims claims = getClaimsFromToken(token); - return claims != null ? claims.getExpiration() : null; - } - - /** - * 从Token中获取Claims - * - * @param token Token - * @return Claims - */ - private Claims getClaimsFromToken(String token) { - try { - return Jwts.parserBuilder() - .setSigningKey(getSecretKey()) - .build() - .parseClaimsJws(token) - .getBody(); - } catch (Exception e) { - log.warn("解析Token失败: {}", e.getMessage()); - return null; - } - } - - /** - * 验证Token是否有效 - * - * @param token Token - * @return 是否有效 - */ - public boolean validateToken(String token) { - if (StrUtil.isBlank(token)) { - return false; - } - - try { - Claims claims = getClaimsFromToken(token); - if (claims == null) { - return false; - } - - // 检查是否过期 - Date expiration = claims.getExpiration(); - return expiration != null && expiration.after(new Date()); - } catch (Exception e) { - log.warn("Token验证失败: {}", e.getMessage()); - return false; - } - } - - /** - * 检查Token是否过期 - * - * @param token Token - * @return 是否过期 - */ - public boolean isTokenExpired(String token) { - Date expiration = getExpirationDateFromToken(token); - return expiration != null && expiration.before(new Date()); - } - - /** - * 刷新Token - * - * @param token 原Token - * @return 新Token - */ - public String refreshToken(String token) { - try { - Claims claims = getClaimsFromToken(token); - if (claims == null) { - return null; - } - - String userId = claims.getSubject(); - String username = claims.get("username", String.class); - - return generateToken(userId, username); - } catch (Exception e) { - log.warn("刷新Token失败: {}", e.getMessage()); - return null; - } - } - - /** - * 从请求头中提取Token - * - * @param authHeader Authorization头 - * @return Token - */ - public String extractTokenFromHeader(String authHeader) { - if (StrUtil.isNotBlank(authHeader) && authHeader.startsWith("Bearer ")) { - return authHeader.substring(7); - } - return null; - } -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/util/SnowflakeIdGenerator.java b/backend/common/src/main/java/com/emotionmuseum/common/util/SnowflakeIdGenerator.java deleted file mode 100644 index bcbd71d..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/util/SnowflakeIdGenerator.java +++ /dev/null @@ -1,234 +0,0 @@ -package com.emotionmuseum.common.util; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -/** - * 雪花算法ID生成器 - * 生成64位长整型ID,转换为字符串避免前端精度丢失问题 - * - * 雪花算法结构: - * 1位符号位(固定为0) + 41位时间戳 + 10位机器ID + 12位序列号 - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Slf4j -@Component -public class SnowflakeIdGenerator { - - /** - * 起始时间戳 (2024-01-01 00:00:00) - */ - private static final long START_TIMESTAMP = 1704067200000L; - - /** - * 机器ID位数 - */ - private static final long MACHINE_ID_BITS = 10L; - - /** - * 序列号位数 - */ - private static final long SEQUENCE_BITS = 12L; - - /** - * 机器ID最大值 - */ - private static final long MAX_MACHINE_ID = ~(-1L << MACHINE_ID_BITS); - - /** - * 序列号最大值 - */ - private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS); - - /** - * 机器ID左移位数 - */ - private static final long MACHINE_ID_SHIFT = SEQUENCE_BITS; - - /** - * 时间戳左移位数 - */ - private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS; - - /** - * 机器ID - */ - private final long machineId; - - /** - * 序列号 - */ - private long sequence = 0L; - - /** - * 上次生成ID的时间戳 - */ - private long lastTimestamp = -1L; - - /** - * 构造函数 - * - * @param machineId 机器ID (0-1023) - */ - public SnowflakeIdGenerator(long machineId) { - if (machineId > MAX_MACHINE_ID || machineId < 0) { - throw new IllegalArgumentException( - String.format("机器ID必须在0到%d之间", MAX_MACHINE_ID)); - } - this.machineId = machineId; - log.info("雪花算法ID生成器初始化完成,机器ID: {}", machineId); - } - - /** - * 默认构造函数,使用默认机器ID - */ - public SnowflakeIdGenerator() { - // 使用当前时间戳的后10位作为默认机器ID - this(System.currentTimeMillis() % (MAX_MACHINE_ID + 1)); - } - - /** - * 生成下一个ID - * - * @return 生成的ID - */ - public synchronized long nextId() { - long timestamp = getCurrentTimestamp(); - - // 如果当前时间小于上次ID生成的时间戳,说明系统时钟回退过,抛出异常 - if (timestamp < lastTimestamp) { - throw new RuntimeException( - String.format("系统时钟回退,拒绝生成ID。当前时间戳: %d, 上次时间戳: %d", - timestamp, lastTimestamp)); - } - - // 如果是同一时间戳,则在序列号上自增 - if (lastTimestamp == timestamp) { - sequence = (sequence + 1) & MAX_SEQUENCE; - // 如果序列号溢出,则等待下一个毫秒 - if (sequence == 0) { - timestamp = getNextTimestamp(lastTimestamp); - } - } else { - // 如果是新的时间戳,则序列号重置为0 - sequence = 0L; - } - - // 更新上次生成ID的时间戳 - lastTimestamp = timestamp; - - // 移位并通过或运算拼到一起组成64位的ID - return ((timestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT) - | (machineId << MACHINE_ID_SHIFT) - | sequence; - } - - /** - * 生成字符串格式的ID - * - * @return 字符串格式的ID - */ - public String nextIdAsString() { - return String.valueOf(nextId()); - } - - /** - * 获取当前时间戳 - * - * @return 当前时间戳 - */ - private long getCurrentTimestamp() { - return System.currentTimeMillis(); - } - - /** - * 获取下一个时间戳 - * - * @param lastTimestamp 上次时间戳 - * @return 下一个时间戳 - */ - private long getNextTimestamp(long lastTimestamp) { - long timestamp = getCurrentTimestamp(); - while (timestamp <= lastTimestamp) { - timestamp = getCurrentTimestamp(); - } - return timestamp; - } - - /** - * 解析ID获取时间戳 - * - * @param id 雪花算法生成的ID - * @return 时间戳 - */ - public long parseTimestamp(long id) { - return (id >> TIMESTAMP_SHIFT) + START_TIMESTAMP; - } - - /** - * 解析ID获取机器ID - * - * @param id 雪花算法生成的ID - * @return 机器ID - */ - public long parseMachineId(long id) { - return (id >> MACHINE_ID_SHIFT) & MAX_MACHINE_ID; - } - - /** - * 解析ID获取序列号 - * - * @param id 雪花算法生成的ID - * @return 序列号 - */ - public long parseSequence(long id) { - return id & MAX_SEQUENCE; - } - - /** - * 获取机器ID - * - * @return 机器ID - */ - public long getMachineId() { - return machineId; - } - - /** - * 批量生成ID - * - * @param count 生成数量 - * @return ID数组 - */ - public long[] nextIds(int count) { - if (count <= 0) { - throw new IllegalArgumentException("生成数量必须大于0"); - } - - long[] ids = new long[count]; - for (int i = 0; i < count; i++) { - ids[i] = nextId(); - } - return ids; - } - - /** - * 批量生成字符串格式的ID - * - * @param count 生成数量 - * @return 字符串ID数组 - */ - public String[] nextIdsAsString(int count) { - if (count <= 0) { - throw new IllegalArgumentException("生成数量必须大于0"); - } - - String[] ids = new String[count]; - for (int i = 0; i < count; i++) { - ids[i] = nextIdAsString(); - } - return ids; - } -} diff --git a/backend/common/src/main/java/com/emotionmuseum/common/util/UserContextUtil.java b/backend/common/src/main/java/com/emotionmuseum/common/util/UserContextUtil.java deleted file mode 100644 index dca8dc4..0000000 --- a/backend/common/src/main/java/com/emotionmuseum/common/util/UserContextUtil.java +++ /dev/null @@ -1,163 +0,0 @@ -package com.emotionmuseum.common.util; - -import com.emotionmuseum.common.handler.EmotionMetaObjectHandler; -import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; - -/** - * 用户上下文工具类 - * 提供手动设置和获取用户上下文的方法 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Slf4j -public class UserContextUtil { - - /** - * 设置当前用户ID - * - * @param userId 用户ID - */ - public static void setCurrentUserId(String userId) { - if (StringUtils.hasText(userId)) { - EmotionMetaObjectHandler.UserContextHolder.setCurrentUserId(userId); - log.debug("手动设置用户上下文: userId={}", userId); - } else { - log.warn("尝试设置空的用户ID"); - } - } - - /** - * 获取当前用户ID - * - * @return 当前用户ID,如果没有则返回null - */ - public static String getCurrentUserId() { - return EmotionMetaObjectHandler.UserContextHolder.getCurrentUserId(); - } - - /** - * 清除当前用户上下文 - */ - public static void clearCurrentUser() { - EmotionMetaObjectHandler.UserContextHolder.clear(); - log.debug("手动清除用户上下文"); - } - - /** - * 检查是否有当前用户 - * - * @return 如果有当前用户返回true,否则返回false - */ - public static boolean hasCurrentUser() { - return StringUtils.hasText(getCurrentUserId()); - } - - /** - * 获取当前用户ID,如果没有则返回默认值 - * - * @param defaultUserId 默认用户ID - * @return 当前用户ID或默认值 - */ - public static String getCurrentUserIdOrDefault(String defaultUserId) { - String currentUserId = getCurrentUserId(); - return StringUtils.hasText(currentUserId) ? currentUserId : defaultUserId; - } - - /** - * 在指定用户上下文中执行操作 - * 执行完成后会恢复原来的用户上下文 - * - * @param userId 临时用户ID - * @param runnable 要执行的操作 - */ - public static void runWithUser(String userId, Runnable runnable) { - String originalUserId = getCurrentUserId(); - try { - setCurrentUserId(userId); - runnable.run(); - } finally { - if (originalUserId != null) { - setCurrentUserId(originalUserId); - } else { - clearCurrentUser(); - } - } - } - - /** - * 在指定用户上下文中执行操作并返回结果 - * 执行完成后会恢复原来的用户上下文 - * - * @param userId 临时用户ID - * @param supplier 要执行的操作 - * @param 返回值类型 - * @return 操作结果 - */ - public static T runWithUser(String userId, java.util.function.Supplier supplier) { - String originalUserId = getCurrentUserId(); - try { - setCurrentUserId(userId); - return supplier.get(); - } finally { - if (originalUserId != null) { - setCurrentUserId(originalUserId); - } else { - clearCurrentUser(); - } - } - } - - /** - * 为访客用户生成临时ID - * - * @return 访客用户ID - */ - public static String generateGuestUserId() { - return "guest_" + System.currentTimeMillis() + "_" + - Integer.toHexString((int)(Math.random() * 0x10000)); - } - - /** - * 检查用户ID是否为访客用户 - * - * @param userId 用户ID - * @return 如果是访客用户返回true - */ - public static boolean isGuestUser(String userId) { - return StringUtils.hasText(userId) && userId.startsWith("guest_"); - } - - /** - * 检查用户ID是否为系统用户 - * - * @param userId 用户ID - * @return 如果是系统用户返回true - */ - public static boolean isSystemUser(String userId) { - return "system".equals(userId); - } - - /** - * 获取用户类型描述 - * - * @param userId 用户ID - * @return 用户类型描述 - */ - public static String getUserTypeDescription(String userId) { - if (!StringUtils.hasText(userId)) { - return "未知用户"; - } - - if (isSystemUser(userId)) { - return "系统用户"; - } - - if (isGuestUser(userId)) { - return "访客用户"; - } - - return "注册用户"; - } -} diff --git a/backend/common/src/test/java/com/emotionmuseum/common/util/SnowflakeIdGeneratorTest.java b/backend/common/src/test/java/com/emotionmuseum/common/util/SnowflakeIdGeneratorTest.java deleted file mode 100644 index 49f64c0..0000000 --- a/backend/common/src/test/java/com/emotionmuseum/common/util/SnowflakeIdGeneratorTest.java +++ /dev/null @@ -1,181 +0,0 @@ -package com.emotionmuseum.common.util; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.BeforeEach; -import static org.junit.jupiter.api.Assertions.*; - -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * 雪花算法ID生成器测试类 - * - * @author huazhongmin - * @since 2025-07-13 - */ -public class SnowflakeIdGeneratorTest { - - private SnowflakeIdGenerator generator; - - @BeforeEach - void setUp() { - generator = new SnowflakeIdGenerator(1L); - } - - @Test - void testNextId() { - long id = generator.nextId(); - assertTrue(id > 0, "生成的ID应该大于0"); - - // 测试连续生成的ID不相同 - long id2 = generator.nextId(); - assertNotEquals(id, id2, "连续生成的ID应该不相同"); - } - - @Test - void testNextIdAsString() { - String id = generator.nextIdAsString(); - assertNotNull(id, "生成的字符串ID不应该为null"); - assertFalse(id.isEmpty(), "生成的字符串ID不应该为空"); - - // 验证是数字字符串 - assertDoesNotThrow(() -> Long.parseLong(id), "生成的字符串应该是有效的数字"); - } - - @Test - void testUniqueIds() { - Set ids = new HashSet<>(); - int count = 10000; - - for (int i = 0; i < count; i++) { - long id = generator.nextId(); - assertTrue(ids.add(id), "生成的ID应该是唯一的: " + id); - } - - assertEquals(count, ids.size(), "应该生成指定数量的唯一ID"); - } - - @Test - void testConcurrentGeneration() throws InterruptedException { - int threadCount = 10; - int idsPerThread = 1000; - ExecutorService executor = Executors.newFixedThreadPool(threadCount); - CountDownLatch latch = new CountDownLatch(threadCount); - Set allIds = new HashSet<>(); - AtomicInteger duplicateCount = new AtomicInteger(0); - - for (int i = 0; i < threadCount; i++) { - executor.submit(() -> { - try { - Set threadIds = new HashSet<>(); - for (int j = 0; j < idsPerThread; j++) { - long id = generator.nextId(); - threadIds.add(id); - } - - synchronized (allIds) { - for (Long id : threadIds) { - if (!allIds.add(id)) { - duplicateCount.incrementAndGet(); - } - } - } - } finally { - latch.countDown(); - } - }); - } - - latch.await(); - executor.shutdown(); - - assertEquals(0, duplicateCount.get(), "并发生成的ID不应该有重复"); - assertEquals(threadCount * idsPerThread, allIds.size(), "应该生成正确数量的唯一ID"); - } - - @Test - void testParseTimestamp() { - long id = generator.nextId(); - long timestamp = generator.parseTimestamp(id); - - // 时间戳应该在合理范围内(当前时间前后1分钟) - long currentTime = System.currentTimeMillis(); - assertTrue(Math.abs(timestamp - currentTime) < 60000, - "解析的时间戳应该接近当前时间"); - } - - @Test - void testParseMachineId() { - long id = generator.nextId(); - long machineId = generator.parseMachineId(id); - - assertEquals(1L, machineId, "解析的机器ID应该等于设置的机器ID"); - } - - @Test - void testParseSequence() { - long id = generator.nextId(); - long sequence = generator.parseSequence(id); - - assertTrue(sequence >= 0 && sequence < 4096, - "解析的序列号应该在0-4095范围内"); - } - - @Test - void testBatchGeneration() { - int count = 100; - long[] ids = generator.nextIds(count); - - assertEquals(count, ids.length, "应该生成指定数量的ID"); - - // 验证所有ID都是唯一的 - Set uniqueIds = new HashSet<>(); - for (long id : ids) { - assertTrue(uniqueIds.add(id), "批量生成的ID应该是唯一的"); - } - } - - @Test - void testBatchGenerationAsString() { - int count = 100; - String[] ids = generator.nextIdsAsString(count); - - assertEquals(count, ids.length, "应该生成指定数量的字符串ID"); - - // 验证所有ID都是唯一的且为有效数字 - Set uniqueIds = new HashSet<>(); - for (String id : ids) { - assertNotNull(id, "字符串ID不应该为null"); - assertFalse(id.isEmpty(), "字符串ID不应该为空"); - assertDoesNotThrow(() -> Long.parseLong(id), "字符串ID应该是有效数字"); - assertTrue(uniqueIds.add(id), "批量生成的字符串ID应该是唯一的"); - } - } - - @Test - void testInvalidMachineId() { - // 测试无效的机器ID - assertThrows(IllegalArgumentException.class, () -> { - new SnowflakeIdGenerator(-1L); - }, "负数机器ID应该抛出异常"); - - assertThrows(IllegalArgumentException.class, () -> { - new SnowflakeIdGenerator(1024L); - }, "超出范围的机器ID应该抛出异常"); - } - - @Test - void testInvalidBatchCount() { - assertThrows(IllegalArgumentException.class, () -> { - generator.nextIds(0); - }, "批量生成数量为0应该抛出异常"); - - assertThrows(IllegalArgumentException.class, () -> { - generator.nextIds(-1); - }, "批量生成数量为负数应该抛出异常"); - } -} diff --git a/backend/deploy-all.sh b/backend/deploy-all.sh deleted file mode 100644 index 799f134..0000000 --- a/backend/deploy-all.sh +++ /dev/null @@ -1,600 +0,0 @@ -#!/bin/bash - -# 情感博物馆 - 全服务容器化部署脚本 -# 作者: emotion-museum -# 日期: 2025-07-18 -# 支持Jenkins CI/CD部署 - -# 不要在遇到错误时立即退出,让所有模块都尝试部署 -set +e - -# 配置变量 - 支持Jenkins环境变量覆盖 -REMOTE_HOST="${DEPLOY_HOST:-'root@101.200.208.45'}" -REMOTE_BUILD_DIR="${REMOTE_BUILD_DIR:-/data/builds}" -REMOTE_DOCKER_COMPOSE_DIR="${REMOTE_DOCKER_DIR:-/data/docker}" -PROFILE="${DEPLOY_ENV:-test}" -PROJECT_NAME="${PROJECT_NAME:-emotion-museum}" - -# Jenkins构建信息 -BUILD_NUMBER="${BUILD_NUMBER:-manual}" -JOB_NAME="${JOB_NAME:-local-deploy}" -BUILD_URL="${BUILD_URL:-}" - -# 部署模式配置 -DEPLOY_MODE="${DEPLOY_MODE:-full}" # full: 完整部署, build: 仅构建, deploy: 仅部署 - -# 颜色输出 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# 日志函数 -log_info() { - echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -# 服务列表 -SERVICES=( - "emotion-gateway:19000" - "emotion-user:19001" - "emotion-ai:19002" - "emotion-record:19003" - "emotion-growth:19004" - "emotion-explore:19005" - "emotion-reward:19006" - "emotion-websocket:19007" - "emotion-auth:19008" - "emotion-stats:19009" -) - -# 如果设置了TEST_SINGLE_SERVICE环境变量,只部署指定服务 -if [ -n "$TEST_SINGLE_SERVICE" ]; then - case $TEST_SINGLE_SERVICE in - "gateway") SERVICES=("emotion-gateway:19000") ;; - "user") SERVICES=("emotion-user:19001") ;; - "ai") SERVICES=("emotion-ai:19002") ;; - *) echo "未知的测试服务: $TEST_SINGLE_SERVICE"; exit 1 ;; - esac - echo "测试模式: 仅部署 $TEST_SINGLE_SERVICE 服务" -fi - -# 部署状态跟踪 -TOTAL_SERVICES=${#SERVICES[@]} -SUCCESSFUL_DEPLOYMENTS=0 -FAILED_DEPLOYMENTS=0 - -# 检查远程服务器连接 -check_remote_connection() { - log_info "检查远程服务器连接..." - if ssh -o ConnectTimeout=10 'root@101.200.208.45' "echo 'Connection successful'" > /dev/null 2>&1; then - log_success "远程服务器连接正常" - else - log_error "无法连接到远程服务器 'root@101.200.208.45'" - exit 1 - fi -} - -# 创建远程目录 -create_remote_directories() { - log_info "创建远程目录结构..." - ssh 'root@101.200.208.45' " - mkdir -p $REMOTE_BUILD_DIR - mkdir -p $REMOTE_DOCKER_COMPOSE_DIR - mkdir -p /data/logs/emotion-museum - mkdir -p /data/config/emotion-museum - " - log_success "远程目录创建完成" -} - -# 构建所有服务 (Jenkins阶段) -build_all_services() { - log_info "开始在Jenkins服务器上构建所有微服务..." - - # 检查是否在Jenkins环境中 - if [ -n "$JENKINS_HOME" ] || [ -n "$BUILD_NUMBER" ]; then - log_info "检测到Jenkins环境,执行完整构建流程" - else - log_info "本地环境,执行构建流程" - fi - - # 先构建父项目 - log_info "构建父项目..." - if mvn clean install -DskipTests -q; then - log_success "父项目构建成功" - else - log_error "父项目构建失败" - exit 1 - fi - - # 构建各个微服务 - for service_info in "${SERVICES[@]}"; do - service_name=$(echo $service_info | cut -d':' -f1) - log_info "构建服务: $service_name" - - cd $service_name - if mvn clean package -DskipTests -P${PROFILE} -q; then - # 检查jar包是否生成 - if [ -f "target/${service_name}-1.0.0.jar" ]; then - local jar_size=$(du -h "target/${service_name}-1.0.0.jar" | cut -f1) - log_success "服务 $service_name 构建成功 (大小: $jar_size)" - else - log_error "服务 $service_name jar包未生成" - cd .. - exit 1 - fi - else - log_error "服务 $service_name 构建失败" - cd .. - exit 1 - fi - cd .. - done - - log_success "所有服务在Jenkins服务器构建完成" -} - -# 部署所有服务到远程服务器 -deploy_all_services_to_remote() { - log_info "开始逐个部署服务到远程服务器..." - - for service_info in "${SERVICES[@]}"; do - service_name=$(echo $service_info | cut -d':' -f1) - service_port=$(echo $service_info | cut -d':' -f2) - - echo "" - log_info "[$((SUCCESSFUL_DEPLOYMENTS + FAILED_DEPLOYMENTS + 1))/$TOTAL_SERVICES] 部署服务: $service_name" - - if deploy_service $service_name $service_port; then - SUCCESSFUL_DEPLOYMENTS=$((SUCCESSFUL_DEPLOYMENTS + 1)) - log_success "✅ 服务 $service_name 部署成功" - else - FAILED_DEPLOYMENTS=$((FAILED_DEPLOYMENTS + 1)) - log_error "❌ 服务 $service_name 部署失败,继续部署其他服务..." - fi - done -} - -# 传输jar包到远程服务器 -transfer_jar_to_remote() { - local service_name=$1 - - log_info "传输jar包到远程服务器: $service_name" - - # 检查本地jar包是否存在 - local jar_file="${service_name}/target/${service_name}-1.0.0.jar" - if [ ! -f "$jar_file" ]; then - log_error "本地JAR包不存在: $jar_file" - return 1 - fi - - # 显示jar包信息 - local jar_size=$(du -h "$jar_file" | cut -f1) - log_info "准备传输jar包: $jar_file (大小: $jar_size)" - - # 删除远程旧jar包 - log_info "清理远程旧jar包: $service_name" - ssh 'root@101.200.208.45' "rm -f $REMOTE_BUILD_DIR/${service_name}-*.jar" - - # 上传新jar包 - log_info "上传jar包到远程服务器..." - if scp "$jar_file" 'root@101.200.208.45':$REMOTE_BUILD_DIR/${service_name}-1.0.0.jar; then - log_success "jar包传输成功: $service_name" - - # 验证远程jar包 - local remote_size=$(ssh 'root@101.200.208.45' "du -h $REMOTE_BUILD_DIR/${service_name}-1.0.0.jar | cut -f1") - log_info "远程jar包大小: $remote_size" - return 0 - else - log_error "jar包传输失败: $service_name" - return 1 - fi -} - -# 部署单个服务 (远程服务器阶段) -deploy_service() { - local service_name=$1 - local service_port=$2 - local start_time=$(date +%s) - - log_info "开始部署服务到远程服务器: $service_name" - - # 先传输jar包 - if ! transfer_jar_to_remote $service_name; then - log_error "jar包传输失败" - return 1 - fi - - # 验证远程jar包存在 - log_info "验证远程jar包: $service_name" - if ! ssh 'root@101.200.208.45' "test -f $REMOTE_BUILD_DIR/${service_name}-1.0.0.jar"; then - local error_msg="远程jar包不存在,请先执行构建和传输" - log_error "$error_msg" - DEPLOYMENT_STATUS[$service_name]="FAILED" - DEPLOYMENT_ERRORS[$service_name]="$error_msg" - return 1 - fi - - # 创建Dockerfile - create_dockerfile $service_name $service_port - - # 停止并删除旧容器 - log_info "停止旧容器: $service_name" - ssh 'root@101.200.208.45' " - docker stop ${service_name} 2>/dev/null || true - docker rm ${service_name} 2>/dev/null || true - docker rmi ${PROJECT_NAME}/${service_name}:latest 2>/dev/null || true - " - - # 构建Docker镜像 - log_info "构建Docker镜像: $service_name" - ssh 'root@101.200.208.45' " - # 复制jar包到Docker构建目录 - cp $REMOTE_BUILD_DIR/${service_name}-1.0.0.jar $REMOTE_DOCKER_COMPOSE_DIR/ - - # 构建镜像 - cd $REMOTE_DOCKER_COMPOSE_DIR - docker build -t ${PROJECT_NAME}/${service_name}:latest -f Dockerfile.${service_name} . - - # 清理临时文件 - rm -f ${service_name}-1.0.0.jar - " - - # 启动新容器 - log_info "启动新容器: $service_name" - ssh 'root@101.200.208.45' " - docker run -d \\ - --name ${service_name} \\ - --network emotion-network \\ - -p ${service_port}:${service_port} \\ - -v /data/logs/emotion-museum:/app/logs \\ - -e SPRING_PROFILES_ACTIVE=${PROFILE} \\ - -e MYSQL_HOST=101.200.208.45 \\ - -e MYSQL_PORT=3306 \\ - -e MYSQL_DATABASE=emotion_museum \\ - -e MYSQL_USERNAME=root \\ - -e MYSQL_PASSWORD='EmotionMuseum2025*#' \\ - -e REDIS_HOST=101.200.208.45 \\ - -e REDIS_PORT=6379 \\ - -e REDIS_PASSWORD= \\ - -e REDIS_DATABASE=0 \\ - -e NACOS_SERVER_ADDR=101.200.208.45:8848 \\ - -e NACOS_USERNAME=nacos \\ - -e NACOS_PASSWORD='Peanut2817*#' \\ - --restart unless-stopped \\ - ${PROJECT_NAME}/${service_name}:latest - " - - # 等待服务启动 - log_info "等待服务启动: $service_name" - sleep 10 - - # 检查容器状态 - if ssh 'root@101.200.208.45' "docker ps | grep ${service_name}" > /dev/null 2>&1; then - log_success "服务 $service_name 启动成功" - - # 显示容器日志 - log_info "显示服务日志 最后10行: $service_name" - ssh 'root@101.200.208.45' "docker logs --tail 10 ${service_name}" 2>/dev/null || true - - # 记录成功状态 - local end_time=$(date +%s) - local duration=$((end_time - start_time)) - DEPLOYMENT_STATUS[$service_name]="SUCCESS" - DEPLOYMENT_TIMES[$service_name]="${duration}s" - return 0 - else - local error_msg="服务启动失败" - log_error "服务 $service_name 启动失败" - log_error "错误日志:" - local error_logs=$(ssh 'root@101.200.208.45' "docker logs ${service_name}" 2>&1 || echo "无法获取日志") - echo "$error_logs" - - # 记录失败状态 - local end_time=$(date +%s) - local duration=$((end_time - start_time)) - DEPLOYMENT_STATUS[$service_name]="FAILED" - DEPLOYMENT_ERRORS[$service_name]="$error_msg: $error_logs" - DEPLOYMENT_TIMES[$service_name]="${duration}s" - return 1 - fi -} - -# 创建Dockerfile -create_dockerfile() { - local service_name=$1 - local service_port=$2 - - log_info "创建Dockerfile: $service_name" - - ssh 'root@101.200.208.45' "cat > $REMOTE_DOCKER_COMPOSE_DIR/Dockerfile.${service_name} << 'EOF' -# 使用Java 17 Alpine镜像 -FROM openjdk:17-alpine - -# 设置工作目录 -WORKDIR /app - -# 安装必要的工具 (Alpine Linux使用apk) -RUN apk add --no-cache curl - -# 复制jar包 (使用相对路径) -COPY ${service_name}-1.0.0.jar app.jar - -# 创建日志目录 -RUN mkdir -p /app/logs - -# 设置时区 -ENV TZ=Asia/Shanghai -RUN ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone - -# 暴露端口 -EXPOSE ${service_port} - -# 健康检查 -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \\ - CMD curl -f http://localhost:${service_port}/actuator/health || exit 1 - -# 启动应用 -ENTRYPOINT [\"java\", \"-Djava.security.egd=file:/dev/./urandom\", \"-Xms512m\", \"-Xmx1024m\", \"-jar\", \"app.jar\"] -EOF" -} - -# 创建Docker网络 -create_docker_network() { - log_info "创建Docker网络..." - ssh 'root@101.200.208.45' " - docker network create emotion-network 2>/dev/null || true - " - log_success "Docker网络创建完成" -} - -# 健康检查 -health_check() { - log_info "执行服务健康检查..." - - for service_info in "${SERVICES[@]}"; do - service_name=$(echo $service_info | cut -d':' -f1) - service_port=$(echo $service_info | cut -d':' -f2) - - log_info "检查服务健康状态: $service_name" - - # 等待服务完全启动 - sleep 5 - - if ssh 'root@101.200.208.45' "curl -f -s http://localhost:${service_port}/actuator/health" > /dev/null 2>&1; then - log_success "服务 $service_name 健康检查通过" - else - log_warning "服务 $service_name 健康检查失败,可能仍在启动中" - fi - done -} - -# 显示详细部署报告 -show_deployment_report() { - local total_time=$1 - - echo "" - echo "========================================" - echo " 部署完成报告" - echo "========================================" - echo "项目名称: $PROJECT_NAME" - echo "部署环境: $PROFILE" - echo "目标服务器: $REMOTE_HOST" - echo "部署时间: $(date '+%Y-%m-%d %H:%M:%S')" - echo "总耗时: ${total_time}s" - if [ "$BUILD_NUMBER" != "manual" ]; then - echo "Jenkins构建: #$BUILD_NUMBER" - echo "Jenkins任务: $JOB_NAME" - [ -n "$BUILD_URL" ] && echo "构建链接: $BUILD_URL" - fi - echo "========================================" - - echo "" - echo "📊 部署统计:" - echo " 总服务数: $TOTAL_SERVICES" - echo " 成功部署: $SUCCESSFUL_DEPLOYMENTS" - echo " 失败部署: $FAILED_DEPLOYMENTS" - echo " 成功率: $(( SUCCESSFUL_DEPLOYMENTS * 100 / TOTAL_SERVICES ))%" - echo "" - - echo "📋 服务部署详情:" - printf "%-20s %-10s %-10s %s\n" "服务名称" "状态" "耗时" "备注" - echo "----------------------------------------" - - for service_info in "${SERVICES[@]}"; do - service_name=$(echo $service_info | cut -d':' -f1) - service_port=$(echo $service_info | cut -d':' -f2) - status=${DEPLOYMENT_STATUS[$service_name]:-"UNKNOWN"} - time=${DEPLOYMENT_TIMES[$service_name]:-"N/A"} - - case $status in - "SUCCESS") - printf "%-20s ${GREEN}%-10s${NC} %-10s %s\n" "$service_name" "✅ 成功" "$time" "http://101.200.208.45:$service_port" - ;; - "FAILED") - printf "%-20s ${RED}%-10s${NC} %-10s %s\n" "$service_name" "❌ 失败" "$time" "查看错误日志" - ;; - *) - printf "%-20s ${YELLOW}%-10s${NC} %-10s %s\n" "$service_name" "⚠️ 未知" "$time" "状态异常" - ;; - esac - done - - echo "" - - # 显示失败服务的错误信息 - if [ $FAILED_DEPLOYMENTS -gt 0 ]; then - echo "❌ 失败服务错误详情:" - echo "----------------------------------------" - for service_info in "${SERVICES[@]}"; do - service_name=$(echo $service_info | cut -d':' -f1) - if [ "${DEPLOYMENT_STATUS[$service_name]}" = "FAILED" ]; then - echo "🔸 $service_name:" - echo " ${DEPLOYMENT_ERRORS[$service_name]}" | head -3 - echo "" - fi - done - fi - - # 显示当前运行的容器状态 - echo "🐳 当前容器运行状态:" - echo "----------------------------------------" - ssh 'root@101.200.208.45' "docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' | grep emotion || echo '没有运行的emotion相关容器'" - - echo "" - echo "========================================" - - # 根据部署结果设置退出码 - if [ $FAILED_DEPLOYMENTS -eq 0 ]; then - echo "🎉 所有服务部署成功!" - return 0 - else - echo "⚠️ 部分服务部署失败,请检查错误日志" - return 1 - fi -} - -# 主函数 -main() { - local start_time=$(date +%s) - - log_info "🚀 开始全服务容器化部署..." - log_info "目标服务器: $REMOTE_HOST" - log_info "部署环境: $PROFILE" - log_info "部署模式: $DEPLOY_MODE" - log_info "服务总数: $TOTAL_SERVICES" - - # 根据部署模式执行不同的流程 - case $DEPLOY_MODE in - "build") - log_info "🔨 执行构建模式 - 仅在Jenkins服务器构建jar包" - execute_build_only - ;; - "deploy") - log_info "🚀 执行部署模式 - 仅部署到远程服务器" - execute_deploy_only - ;; - "full"|*) - log_info "🔄 执行完整模式 - 构建+部署" - execute_full_deployment - ;; - esac -} - -# 仅构建模式 -execute_build_only() { - local start_time=$(date +%s) - - log_info "开始构建所有服务..." - - # 构建服务 - if ! build_all_services; then - log_error "服务构建失败" - exit 1 - fi - - # 显示构建结果 - log_info "📦 构建产物信息:" - for service_info in "${SERVICES[@]}"; do - service_name=$(echo $service_info | cut -d':' -f1) - jar_file="${service_name}/target/${service_name}-1.0.0.jar" - if [ -f "$jar_file" ]; then - jar_size=$(du -h "$jar_file" | cut -f1) - log_success "✅ $service_name: $jar_size" - else - log_error "❌ $service_name: jar包未生成" - fi - done - - local end_time=$(date +%s) - local total_time=$((end_time - start_time)) - log_success "🎉 构建完成!总耗时: ${total_time}s" -} - -# 仅部署模式 -execute_deploy_only() { - local start_time=$(date +%s) - - log_info "开始部署到远程服务器..." - - # 检查连接 - if ! check_remote_connection; then - log_error "远程服务器连接失败,部署终止" - exit 1 - fi - - # 创建目录和网络 - create_remote_directories - create_docker_network - - # 部署所有服务 - deploy_all_services_to_remote - - # 健康检查和报告 - health_check - local end_time=$(date +%s) - local total_time=$((end_time - start_time)) - show_deployment_report $total_time -} - -# 完整部署模式 -execute_full_deployment() { - local start_time=$(date +%s) - - # 检查连接 - if ! check_remote_connection; then - log_error "远程服务器连接失败,部署终止" - exit 1 - fi - - # 创建目录 - create_remote_directories - - # 创建Docker网络 - create_docker_network - - # 构建服务 - if ! build_all_services; then - log_error "服务构建失败,部署终止" - exit 1 - fi - - # 部署所有服务 - deploy_all_services_to_remote - - # 健康检查 - log_info "执行服务健康检查..." - health_check - - # 计算总耗时 - local end_time=$(date +%s) - local total_time=$((end_time - start_time)) - - # 显示详细报告 - show_deployment_report $total_time - - # 根据部署结果设置退出码 - if [ $FAILED_DEPLOYMENTS -eq 0 ]; then - log_success "🎉 全服务容器化部署完成!" - exit 0 - else - log_warning "⚠️ 部分服务部署失败,请查看详细报告" - exit 1 - fi -} - -# 执行主函数 -main "$@" diff --git a/backend/deploy-remote.sh b/backend/deploy-remote.sh deleted file mode 100644 index 33bbb73..0000000 --- a/backend/deploy-remote.sh +++ /dev/null @@ -1,358 +0,0 @@ -#!/bin/bash - -# 情感博物馆 - 远程部署脚本 -# 作者: emotion-museum -# 日期: 2025-07-18 -# 用途: 将构建好的jar包部署到远程服务器 - -set -e - -# 配置变量 -REMOTE_HOST="'root@101.200.208.45'" -REMOTE_BUILD_DIR="/data/builds" -REMOTE_DOCKER_COMPOSE_DIR="/data/docker" -PROFILE="${DEPLOY_ENV:-test}" -PROJECT_NAME="${PROJECT_NAME:-emotion-museum}" - -# Jenkins构建信息 -BUILD_NUMBER="${BUILD_NUMBER:-manual}" -JOB_NAME="${JOB_NAME:-local-deploy}" -BUILD_URL="${BUILD_URL:-}" - -# 颜色输出 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -# 日志函数 -log_info() { - echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -# 服务列表 -SERVICES=( - "emotion-gateway:19000" - "emotion-user:19001" - "emotion-ai:19002" - "emotion-record:19003" - "emotion-growth:19004" - "emotion-explore:19005" - "emotion-reward:19006" - "emotion-websocket:19007" - "emotion-auth:19008" - "emotion-stats:19009" -) - -# 部署状态跟踪 -TOTAL_SERVICES=${#SERVICES[@]} -SUCCESSFUL_DEPLOYMENTS=0 -FAILED_DEPLOYMENTS=0 - -# 检查远程服务器连接 -check_remote_connection() { - log_info "检查远程服务器连接..." - if ssh -o ConnectTimeout=10 'root@101.200.208.45' "echo 'Connection successful'" > /dev/null 2>&1; then - log_success "远程服务器连接正常" - else - log_error "无法连接到远程服务器 'root@101.200.208.45'" - exit 1 - fi -} - -# 检查本地jar包 -check_local_jars() { - log_info "检查本地jar包..." - - local missing_jars=0 - for service_info in "${SERVICES[@]}"; do - service_name=$(echo $service_info | cut -d':' -f1) - jar_file="${service_name}/target/${service_name}-1.0.0.jar" - - if [ -f "$jar_file" ]; then - jar_size=$(du -h "$jar_file" | cut -f1) - log_info "✅ $service_name: $jar_size" - else - log_error "❌ $service_name: jar包不存在" - missing_jars=$((missing_jars + 1)) - fi - done - - if [ $missing_jars -gt 0 ]; then - log_error "发现 $missing_jars 个缺失的jar包,请先执行构建" - exit 1 - fi - - log_success "所有jar包检查通过" -} - -# 传输所有jar包到远程服务器 -transfer_all_jars() { - log_info "开始传输所有jar包到远程服务器..." - - # 创建远程目录 - ssh 'root@101.200.208.45' "mkdir -p $REMOTE_BUILD_DIR" - - local transfer_success=0 - local transfer_failed=0 - - for service_info in "${SERVICES[@]}"; do - service_name=$(echo $service_info | cut -d':' -f1) - jar_file="${service_name}/target/${service_name}-1.0.0.jar" - - if [ ! -f "$jar_file" ]; then - log_warning "跳过不存在的jar包: $service_name" - continue - fi - - log_info "传输jar包: $service_name" - - # 删除远程旧jar包 - ssh 'root@101.200.208.45' "rm -f $REMOTE_BUILD_DIR/${service_name}-*.jar" - - # 上传新jar包 - if scp "$jar_file" 'root@101.200.208.45':$REMOTE_BUILD_DIR/${service_name}-1.0.0.jar; then - # 验证远程jar包 - remote_size=$(ssh 'root@101.200.208.45' "du -h $REMOTE_BUILD_DIR/${service_name}-1.0.0.jar | cut -f1") - log_success "✅ $service_name 传输成功 (远程大小: $remote_size)" - transfer_success=$((transfer_success + 1)) - else - log_error "❌ $service_name 传输失败" - transfer_failed=$((transfer_failed + 1)) - fi - done - - log_info "传输统计: 成功 $transfer_success, 失败 $transfer_failed" - - if [ $transfer_failed -eq 0 ]; then - log_success "所有jar包传输成功" - return 0 - else - log_error "部分jar包传输失败" - return 1 - fi -} - -# 创建远程目录 -create_remote_directories() { - log_info "创建远程目录结构..." - ssh 'root@101.200.208.45' " - mkdir -p $REMOTE_BUILD_DIR - mkdir -p $REMOTE_DOCKER_COMPOSE_DIR - mkdir -p /data/logs/emotion-museum - mkdir -p /data/config/emotion-museum - " - log_success "远程目录创建完成" -} - -# 创建Docker网络 -create_docker_network() { - log_info "创建Docker网络..." - ssh 'root@101.200.208.45' " - docker network create emotion-network 2>/dev/null || true - " - log_success "Docker网络创建完成" -} - -# 部署单个服务到远程 -deploy_service_to_remote() { - local service_name=$1 - local service_port=$2 - local start_time=$(date +%s) - - log_info "部署服务到远程: $service_name" - - # 验证远程jar包存在 - if ! ssh 'root@101.200.208.45' "test -f $REMOTE_BUILD_DIR/${service_name}-1.0.0.jar"; then - local error_msg="远程jar包不存在" - log_error "$error_msg" - return 1 - fi - - # 创建Dockerfile - create_dockerfile $service_name $service_port - - # 停止并删除旧容器 - log_info "停止旧容器: $service_name" - ssh 'root@101.200.208.45' " - docker stop ${service_name} 2>/dev/null || true - docker rm ${service_name} 2>/dev/null || true - docker rmi ${PROJECT_NAME}/${service_name}:latest 2>/dev/null || true - " - - # 构建Docker镜像 - log_info "构建Docker镜像: $service_name" - ssh 'root@101.200.208.45' " - # 复制jar包到Docker构建目录 - cp $REMOTE_BUILD_DIR/${service_name}-1.0.0.jar $REMOTE_DOCKER_COMPOSE_DIR/ - - # 构建镜像 - cd $REMOTE_DOCKER_COMPOSE_DIR - docker build -t ${PROJECT_NAME}/${service_name}:latest -f Dockerfile.${service_name} . - - # 清理临时文件 - rm -f ${service_name}-1.0.0.jar - " - - # 启动新容器 - log_info "启动新容器: $service_name" - ssh 'root@101.200.208.45' " - docker run -d \\ - --name ${service_name} \\ - --network emotion-network \\ - -p ${service_port}:${service_port} \\ - -v /data/logs/emotion-museum:/app/logs \\ - -e SPRING_PROFILES_ACTIVE=${PROFILE} \\ - -e MYSQL_HOST=101.200.208.45 \\ - -e MYSQL_PORT=3306 \\ - -e MYSQL_DATABASE=emotion_museum \\ - -e MYSQL_USERNAME=root \\ - -e MYSQL_PASSWORD='EmotionMuseum2025*#' \\ - -e REDIS_HOST=101.200.208.45 \\ - -e REDIS_PORT=6379 \\ - -e REDIS_PASSWORD= \\ - -e REDIS_DATABASE=0 \\ - -e NACOS_SERVER_ADDR=101.200.208.45:8848 \\ - -e NACOS_USERNAME=nacos \\ - -e NACOS_PASSWORD='Peanut2817*#' \\ - --restart unless-stopped \\ - ${PROJECT_NAME}/${service_name}:latest - " - - # 等待服务启动 - log_info "等待服务启动: $service_name" - sleep 10 - - # 检查容器状态 - if ssh 'root@101.200.208.45' "docker ps | grep ${service_name}" > /dev/null 2>&1; then - log_success "服务 $service_name 启动成功" - - # 显示容器日志 - log_info "显示服务日志 最后10行: $service_name" - ssh 'root@101.200.208.45' "docker logs --tail 10 ${service_name}" 2>/dev/null || true - - # 记录成功状态 - local end_time=$(date +%s) - local duration=$((end_time - start_time)) - log_info "服务 $service_name 部署成功,耗时: ${duration}s" - return 0 - else - local error_msg="服务启动失败" - log_error "服务 $service_name 启动失败" - local error_logs=$(ssh 'root@101.200.208.45' "docker logs ${service_name}" 2>&1 || echo "无法获取日志") - echo "$error_logs" - - # 记录失败状态 - local end_time=$(date +%s) - local duration=$((end_time - start_time)) - log_error "服务 $service_name 部署失败,耗时: ${duration}s" - return 1 - fi -} - -# 创建Dockerfile -create_dockerfile() { - local service_name=$1 - local service_port=$2 - - log_info "创建Dockerfile: $service_name" - - ssh 'root@101.200.208.45' "cat > $REMOTE_DOCKER_COMPOSE_DIR/Dockerfile.${service_name} << 'EOF' -# 使用Java 17 Alpine镜像 -FROM openjdk:17-alpine - -WORKDIR /app - -# 安装必要的工具 (Alpine Linux使用apk) -RUN apk add --no-cache curl - -COPY ${service_name}-1.0.0.jar app.jar - -RUN mkdir -p /app/logs - -ENV TZ=Asia/Shanghai -RUN ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone - -EXPOSE ${service_port} - -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \\ - CMD curl -f http://localhost:${service_port}/actuator/health || exit 1 - -ENTRYPOINT [\"java\", \"-Djava.security.egd=file:/dev/./urandom\", \"-Xms512m\", \"-Xmx1024m\", \"-jar\", \"app.jar\"] -EOF" -} - -# 主函数 -main() { - local start_time=$(date +%s) - - log_info "🚀 开始远程部署任务..." - log_info "目标服务器: $REMOTE_HOST" - log_info "部署环境: $PROFILE" - log_info "服务总数: $TOTAL_SERVICES" - - # 检查远程连接 - check_remote_connection - - # 检查本地jar包 - check_local_jars - - # 创建远程目录和网络 - create_remote_directories - create_docker_network - - # 传输所有jar包 - if ! transfer_all_jars; then - log_error "jar包传输失败,部署终止" - exit 1 - fi - - # 部署所有服务 - log_info "开始逐个部署服务..." - for service_info in "${SERVICES[@]}"; do - service_name=$(echo $service_info | cut -d':' -f1) - service_port=$(echo $service_info | cut -d':' -f2) - - echo "" - log_info "[$((SUCCESSFUL_DEPLOYMENTS + FAILED_DEPLOYMENTS + 1))/$TOTAL_SERVICES] 部署服务: $service_name" - - if deploy_service_to_remote $service_name $service_port; then - SUCCESSFUL_DEPLOYMENTS=$((SUCCESSFUL_DEPLOYMENTS + 1)) - log_success "✅ 服务 $service_name 部署成功" - else - FAILED_DEPLOYMENTS=$((FAILED_DEPLOYMENTS + 1)) - log_error "❌ 服务 $service_name 部署失败,继续部署其他服务..." - fi - done - - # 计算总耗时 - local end_time=$(date +%s) - local total_time=$((end_time - start_time)) - - log_info "部署统计: 成功 $SUCCESSFUL_DEPLOYMENTS, 失败 $FAILED_DEPLOYMENTS" - - # 根据部署结果设置退出码 - if [ $FAILED_DEPLOYMENTS -eq 0 ]; then - log_success "🎉 远程部署任务完成!总耗时: ${total_time}s" - exit 0 - else - log_warning "⚠️ 部分服务部署失败,总耗时: ${total_time}s" - exit 1 - fi -} - -# 执行主函数 -main "$@" diff --git a/backend/explore/Dockerfile b/backend/explore/Dockerfile deleted file mode 100644 index 3786852..0000000 --- a/backend/explore/Dockerfile +++ /dev/null @@ -1,48 +0,0 @@ -# 探索服务Dockerfile -FROM openjdk:17-jdk-alpine - -# 设置工作目录 -WORKDIR /app - -# 安装必要的工具 -RUN apk add --no-cache curl tzdata && \ - cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ - echo "Asia/Shanghai" > /etc/timezone - -# 复制Maven构建文件 -COPY pom.xml ./ -COPY emotion-common ./emotion-common -COPY emotion-explore ./emotion-explore - -# 安装Maven -RUN apk add --no-cache maven - -# 构建应用 -RUN mvn clean package -DskipTests -pl emotion-explore -am - -# 创建运行用户 -RUN addgroup -g 1000 emotion && \ - adduser -D -s /bin/sh -u 1000 -G emotion emotion - -# 复制jar文件 -RUN cp emotion-explore/target/emotion-explore-*.jar app.jar - -# 设置文件权限 -RUN chown -R emotion:emotion /app - -# 切换到非root用户 -USER emotion - -# 健康检查 -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ - CMD curl -f http://localhost:19005/actuator/health || exit 1 - -# 暴露端口 -EXPOSE 19005 - -# 启动命令 -ENTRYPOINT ["java", "-jar", \ - "-Xms512m", "-Xmx1024m", \ - "-Djava.security.egd=file:/dev/./urandom", \ - "-Dspring.profiles.active=local", \ - "app.jar"] diff --git a/backend/explore/api/pom.xml b/backend/explore/api/pom.xml deleted file mode 100644 index 18b6592..0000000 --- a/backend/explore/api/pom.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - explore - 1.0.0 - ../pom.xml - - explore-api - explore-api - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - com.emotionmuseum - common - ${project.version} - - - diff --git a/backend/explore/api/src/main/java/com/emotionmuseum/explore/api/client/ExploreClient.java b/backend/explore/api/src/main/java/com/emotionmuseum/explore/api/client/ExploreClient.java deleted file mode 100644 index 78a3333..0000000 --- a/backend/explore/api/src/main/java/com/emotionmuseum/explore/api/client/ExploreClient.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.emotionmuseum.explore.api.client; - -import com.emotionmuseum.common.result.Result; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; - -@FeignClient(name = "explore") -public interface ExploreClient { - - @GetMapping("/explore/health") - Result health(); -} - - diff --git a/backend/explore/deploy.sh b/backend/explore/deploy.sh deleted file mode 100644 index 1046750..0000000 --- a/backend/explore/deploy.sh +++ /dev/null @@ -1,226 +0,0 @@ -#!/bin/bash - -# emotion-explore 单独部署脚本 -# 作者: emotion-museum -# 日期: 2025-07-18 - -set -e - -# 配置变量 -SERVICE_NAME="emotion-explore" -SERVICE_PORT="19005" -REMOTE_HOST="'root@101.200.208.45'" -REMOTE_BUILD_DIR="/data/builds" -REMOTE_DOCKER_COMPOSE_DIR="/data/docker" -PROFILE="test" -PROJECT_NAME="emotion-museum" - -# 颜色输出 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -# 日志函数 -log_info() { - echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -# 检查远程服务器连接 -check_remote_connection() { - log_info "检查远程服务器连接..." - if ssh -o ConnectTimeout=10 'root@101.200.208.45' "echo 'Connection successful'" > /dev/null 2>&1; then - log_success "远程服务器连接正常" - else - log_error "无法连接到远程服务器 'root@101.200.208.45'" - exit 1 - fi -} - -# 构建服务 -build_service() { - log_info "构建服务: $SERVICE_NAME" - - # 构建父项目依赖 - cd .. - mvn clean install -DskipTests -q - cd emotion-explore - - # 构建当前服务 - if mvn clean package -DskipTests -Ptest -q; then - log_success "服务 $SERVICE_NAME 构建成功" - else - log_error "服务 $SERVICE_NAME 构建失败" - exit 1 - fi -} - -# 创建Dockerfile -create_dockerfile() { - log_info "创建Dockerfile: $SERVICE_NAME" - - ssh 'root@101.200.208.45' "cat > $REMOTE_DOCKER_COMPOSE_DIR/Dockerfile.${SERVICE_NAME} << 'EOF' -# 使用阿里云镜像源的OpenJDK -# 使用Java 17 Alpine镜像 -FROM openjdk:17-alpine - -WORKDIR /app - -# 安装必要的工具 (Alpine Linux使用apk) -RUN apk add --no-cache curl - -COPY ${SERVICE_NAME}-1.0.0.jar app.jar - -RUN mkdir -p /app/logs - -ENV TZ=Asia/Shanghai -RUN ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone - -EXPOSE ${SERVICE_PORT} - -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \\ - CMD curl -f http://localhost:${SERVICE_PORT}/actuator/health || exit 1 - -ENTRYPOINT [\"java\", \"-Djava.security.egd=file:/dev/./urandom\", \"-Xms512m\", \"-Xmx1024m\", \"-jar\", \"app.jar\"] -EOF" -} - -# 部署服务 -deploy_service() { - log_info "开始部署服务: $SERVICE_NAME" - - # 检查jar包 - local jar_file="target/${SERVICE_NAME}-1.0.0.jar" - if [ ! -f "$jar_file" ]; then - log_error "JAR包不存在: $jar_file" - exit 1 - fi - - # 创建远程目录 - ssh 'root@101.200.208.45' " - mkdir -p $REMOTE_BUILD_DIR - mkdir -p $REMOTE_DOCKER_COMPOSE_DIR - mkdir -p /data/logs/emotion-museum - " - - # 删除旧jar包 - log_info "删除远程旧jar包" - ssh 'root@101.200.208.45' "rm -f $REMOTE_BUILD_DIR/${SERVICE_NAME}-*.jar" - - # 上传新jar包 - log_info "上传jar包" - if scp "$jar_file" 'root@101.200.208.45':$REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar; then - log_success "jar包上传成功" - else - log_error "jar包上传失败" - exit 1 - fi - - # 创建Dockerfile - create_dockerfile - - # 停止旧容器 - log_info "停止旧容器" - ssh 'root@101.200.208.45' " - docker stop ${SERVICE_NAME} 2>/dev/null || true - docker rm ${SERVICE_NAME} 2>/dev/null || true - docker rmi ${PROJECT_NAME}/${SERVICE_NAME}:latest 2>/dev/null || true - " - - # 创建Docker网络 - ssh 'root@101.200.208.45' "docker network create emotion-network 2>/dev/null || true" - - # 构建镜像 - log_info "构建Docker镜像" - ssh 'root@101.200.208.45' " - cd $REMOTE_DOCKER_COMPOSE_DIR - # 复制jar包到Docker构建目录 - cp $REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar $REMOTE_DOCKER_COMPOSE_DIR/ - - # 构建镜像 docker build -t ${PROJECT_NAME}/${SERVICE_NAME}:latest -f Dockerfile.${SERVICE_NAME} . - - # 清理临时文件 - rm -f ${SERVICE_NAME}-1.0.0.jar " - - # 启动容器 - log_info "启动新容器" - ssh 'root@101.200.208.45' " - docker run -d \\ - --name ${SERVICE_NAME} \\ - --network emotion-network \\ - -p ${SERVICE_PORT}:${SERVICE_PORT} \\ - -v /data/logs/emotion-museum:/app/logs \\ - -e SPRING_PROFILES_ACTIVE=${PROFILE} \\ - -e MYSQL_HOST=101.200.208.45 \\ - -e MYSQL_PORT=3306 \\ - -e MYSQL_DATABASE=emotion_museum \\ - -e MYSQL_USERNAME=root \\ - -e MYSQL_PASSWORD='EmotionMuseum2025*#' \\ - -e REDIS_HOST=101.200.208.45 \\ - -e REDIS_PORT=6379 \\ - -e REDIS_PASSWORD= \\ - -e REDIS_DATABASE=0 \\ - -e NACOS_SERVER_ADDR=101.200.208.45:8848 \\ - -e NACOS_USERNAME=nacos \\ - -e NACOS_PASSWORD='Peanut2817*#' \\ - --restart unless-stopped \\ - ${PROJECT_NAME}/${SERVICE_NAME}:latest - " - - # 等待启动 - log_info "等待服务启动..." - sleep 15 - - # 检查状态 - if ssh 'root@101.200.208.45' "docker ps | grep ${SERVICE_NAME}" > /dev/null; then - log_success "服务启动成功" - - # 显示日志 - log_info "服务日志 最后20行:" - ssh 'root@101.200.208.45' "docker logs --tail 20 ${SERVICE_NAME}" - - # 健康检查 - log_info "执行健康检查..." - sleep 10 - if ssh 'root@101.200.208.45' "curl -f -s http://localhost:${SERVICE_PORT}/actuator/health" > /dev/null 2>&1; then - log_success "健康检查通过" - else - log_warning "健康检查失败,服务可能仍在启动中" - fi - else - log_error "服务启动失败" - ssh 'root@101.200.208.45' "docker logs ${SERVICE_NAME}" - exit 1 - fi -} - -# 主函数 -main() { - log_info "开始部署 $SERVICE_NAME 服务" - log_info "目标服务器: $REMOTE_HOST" - log_info "服务端口: $SERVICE_PORT" - log_info "部署环境: $PROFILE" - - check_remote_connection - build_service - deploy_service - - log_success "$SERVICE_NAME 服务部署完成!" - log_info "访问地址: http://101.200.208.45:$SERVICE_PORT" -} - -# 执行主函数 -main "$@" diff --git a/backend/explore/pom.xml b/backend/explore/pom.xml deleted file mode 100644 index 5f3592e..0000000 --- a/backend/explore/pom.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - backend - 1.0.0 - ../pom.xml - - explore - pom - - api - server - - diff --git a/backend/explore/server/pom.xml b/backend/explore/server/pom.xml deleted file mode 100644 index 7eeeb93..0000000 --- a/backend/explore/server/pom.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - 4.0.0 - - - com.emotionmuseum - explore - 1.0.0 - - - explore-server - explore - 地图探索服务 - - - - com.emotionmuseum - explore-api - ${project.version} - - - com.emotionmuseum - common - ${project.version} - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-validation - - - com.baomidou - mybatis-plus-boot-starter - - - mysql - mysql-connector-java - - - com.alibaba - druid-spring-boot-starter - - - org.springframework.boot - spring-boot-starter-data-redis - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-discovery - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-config - - - org.springframework.boot - spring-boot-starter-actuator - - - \ No newline at end of file diff --git a/backend/explore/server/src/main/java/com/emotionmuseum/explore/ExploreApplication.java b/backend/explore/server/src/main/java/com/emotionmuseum/explore/ExploreApplication.java deleted file mode 100644 index 5577aa2..0000000 --- a/backend/explore/server/src/main/java/com/emotionmuseum/explore/ExploreApplication.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.emotionmuseum.explore; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - - -/** - * 地图探索服务启动类 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@SpringBootApplication -@MapperScan("com.emotionmuseum.explore.mapper") -public class ExploreApplication { - - public static void main(String[] args) { - SpringApplication.run(ExploreApplication.class, args); - } -} diff --git a/backend/explore/server/src/main/java/com/emotionmuseum/explore/entity/Comment.java b/backend/explore/server/src/main/java/com/emotionmuseum/explore/entity/Comment.java deleted file mode 100644 index 5ffaa28..0000000 --- a/backend/explore/server/src/main/java/com/emotionmuseum/explore/entity/Comment.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.emotionmuseum.explore.entity; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import com.emotionmuseum.common.entity.BaseEntity; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - * 评论实体 - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName("comment") -public class Comment extends BaseEntity { - - /** - * 帖子ID - */ - @TableField("post_id") - private String postId; - - /** - * 用户ID - */ - @TableField("user_id") - private String userId; - - /** - * 评论内容 - */ - @TableField("content") - private String content; - - /** - * 回复的评论ID - */ - @TableField("reply_to_id") - private String replyToId; - - /** - * 点赞数 - */ - @TableField("likes") - private Integer likes; -} diff --git a/backend/explore/server/src/main/java/com/emotionmuseum/explore/entity/CommunityPost.java b/backend/explore/server/src/main/java/com/emotionmuseum/explore/entity/CommunityPost.java deleted file mode 100644 index 7ceb29d..0000000 --- a/backend/explore/server/src/main/java/com/emotionmuseum/explore/entity/CommunityPost.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.emotionmuseum.explore.entity; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import com.emotionmuseum.common.entity.BaseEntity; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.List; - -/** - * 社区帖子实体 - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName(value = "community_post", autoResultMap = true) -public class CommunityPost extends BaseEntity { - - /** - * 用户ID - */ - @TableField("user_id") - private String userId; - - /** - * 地点ID - */ - @TableField("location_id") - private String locationId; - - /** - * 标题 - */ - @TableField("title") - private String title; - - /** - * 内容 - */ - @TableField("content") - private String content; - - /** - * 帖子类型 - */ - @TableField("type") - private String type; - - /** - * 图片列表 - */ - @TableField(value = "images", typeHandler = JacksonTypeHandler.class) - private List images; - - /** - * 标签 - */ - @TableField(value = "tags", typeHandler = JacksonTypeHandler.class) - private List tags; - - /** - * 点赞数 - */ - @TableField("likes") - private Integer likes; - - /** - * 浏览数 - */ - @TableField("view_count") - private Integer viewCount; - - /** - * 评论数 - */ - @TableField("comment_count") - private Integer commentCount; - - /** - * 是否私密: 0-公开, 1-私密 - */ - @TableField("is_private") - private Integer isPrivate; -} diff --git a/backend/explore/server/src/main/java/com/emotionmuseum/explore/entity/LocationPin.java b/backend/explore/server/src/main/java/com/emotionmuseum/explore/entity/LocationPin.java deleted file mode 100644 index 26f6083..0000000 --- a/backend/explore/server/src/main/java/com/emotionmuseum/explore/entity/LocationPin.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.emotionmuseum.explore.entity; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import com.emotionmuseum.common.entity.BaseEntity; -import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.math.BigDecimal; -import java.time.LocalDateTime; - -/** - * 地点标记实体 - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName("location_pin") -public class LocationPin extends BaseEntity { - - /** - * 地点名称 - */ - @TableField("name") - private String name; - - /** - * 地点类型 - */ - @TableField("type") - private String type; - - /** - * 地点分类 - */ - @TableField("category") - private String category; - - /** - * 纬度 - */ - @TableField("latitude") - private BigDecimal latitude; - - /** - * 经度 - */ - @TableField("longitude") - private BigDecimal longitude; - - /** - * 地址 - */ - @TableField("address") - private String address; - - /** - * 描述 - */ - @TableField("description") - private String description; - - /** - * 创建者 - */ - @TableField("created_by") - private String createdBy; - - /** - * 点赞数 - */ - @TableField("likes") - private Integer likes; - - /** - * 访问数 - */ - @TableField("visits") - private Integer visits; - - /** - * 是否收藏: 0-未收藏, 1-已收藏 - */ - @TableField("is_bookmarked") - private Integer isBookmarked; - - /** - * 最后访问时间 - */ - @TableField("last_visit_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime lastVisitTime; -} diff --git a/backend/explore/server/src/main/resources/application-local.yml b/backend/explore/server/src/main/resources/application-local.yml deleted file mode 100644 index d813547..0000000 --- a/backend/explore/server/src/main/resources/application-local.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 本地开发环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: localhost:8848 - namespace: - group: DEFAULT_GROUP - enabled: true - username: nacos - password: Peanut2817*# - metadata: - version: 1.0.0 - zone: local - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: localhost:8848 - namespace: - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: Peanut2817*# - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: 123456 - - # Redis配置 - data: - redis: - host: localhost - port: 6379 - password: - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: debug - com.baomidou.mybatisplus: debug - com.alibaba.nacos: info - file: - name: logs/emotion-explore-local.log diff --git a/backend/explore/server/src/main/resources/application-prod.yml b/backend/explore/server/src/main/resources/application-prod.yml deleted file mode 100644 index 27546cc..0000000 --- a/backend/explore/server/src/main/resources/application-prod.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 生产环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: prod - group: DEFAULT_GROUP - enabled: true - username: nacos - password: EmotionMuseum2025 - metadata: - version: 1.0.0 - zone: prod - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: 101.200.208.45:8848 - namespace: prod - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: EmotionMuseum2025 - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://101.200.208.45:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: EmotionMuseum2025*# - - # Redis配置 - data: - redis: - host: 101.200.208.45 - port: 6379 - password: EmotionMuseum2025*# - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: warn - com.baomidou.mybatisplus: warn - com.alibaba.nacos: error - file: - name: logs/emotion-explore-prod.log diff --git a/backend/explore/server/src/main/resources/application-test.yml b/backend/explore/server/src/main/resources/application-test.yml deleted file mode 100644 index 8b00835..0000000 --- a/backend/explore/server/src/main/resources/application-test.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 测试环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: test - group: DEFAULT_GROUP - enabled: true - username: nacos - password: EmotionMuseum2025 - metadata: - version: 1.0.0 - zone: test - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: 101.200.208.45:8848 - namespace: test - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: EmotionMuseum2025 - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://101.200.208.45:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: EmotionMuseum2025*# - - # Redis配置 - data: - redis: - host: 101.200.208.45 - port: 6379 - password: EmotionMuseum2025*# - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: info - com.baomidou.mybatisplus: info - com.alibaba.nacos: warn - file: - name: logs/emotion-explore-test.log diff --git a/backend/explore/server/src/main/resources/application.yml b/backend/explore/server/src/main/resources/application.yml deleted file mode 100644 index 17bcbd1..0000000 --- a/backend/explore/server/src/main/resources/application.yml +++ /dev/null @@ -1,73 +0,0 @@ -server: - port: 19005 - -spring: - application: - name: emotion-explore - profiles: - active: dev - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: emotion-dev - group: DEFAULT_GROUP - enabled: false - config: - enabled: false - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 - username: root - password: 123456 - type: com.alibaba.druid.pool.DruidDataSource - druid: - initial-size: 5 - min-idle: 5 - max-active: 20 - max-wait: 60000 - time-between-eviction-runs-millis: 60000 - min-evictable-idle-time-millis: 300000 - validation-query: SELECT 1 FROM DUAL - test-while-idle: true - test-on-borrow: false - test-on-return: false - pool-prepared-statements: true - max-pool-prepared-statement-per-connection-size: 20 -redis: - data: - host: localhost - port: 6379 - password: - database: 0 - timeout: 10000ms - lettuce: - pool: - max-active: 8 - max-wait: -1ms - max-idle: 8 - min-idle: 0 - -# 监控配置 -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus - endpoint: - health: - show-details: always - metrics: - export: - prometheus: - enabled: true - -# 日志配置 -logging: - file: - path: /data/logs/emotion-museum/explore - level: - com.emotionmuseum: debug - com.baomidou.mybatisplus: debug - pattern: - console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%logger{50}] - %msg%n" diff --git a/backend/gateway/Dockerfile b/backend/gateway/Dockerfile deleted file mode 100644 index f06a8b4..0000000 --- a/backend/gateway/Dockerfile +++ /dev/null @@ -1,48 +0,0 @@ -# 网关服务Dockerfile -FROM openjdk:17-jdk-alpine - -# 设置工作目录 -WORKDIR /app - -# 安装必要的工具 -RUN apk add --no-cache curl tzdata && \ - cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ - echo "Asia/Shanghai" > /etc/timezone - -# 复制Maven构建文件 -COPY pom.xml ./ -COPY emotion-common ./emotion-common -COPY emotion-gateway ./emotion-gateway - -# 安装Maven -RUN apk add --no-cache maven - -# 构建应用 -RUN mvn clean package -DskipTests -pl emotion-gateway -am - -# 创建运行用户 -RUN addgroup -g 1000 emotion && \ - adduser -D -s /bin/sh -u 1000 -G emotion emotion - -# 复制jar文件 -RUN cp emotion-gateway/target/emotion-gateway-*.jar app.jar - -# 设置文件权限 -RUN chown -R emotion:emotion /app - -# 切换到非root用户 -USER emotion - -# 健康检查 -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ - CMD curl -f http://localhost:19000/actuator/health || exit 1 - -# 暴露端口 -EXPOSE 19000 - -# 启动命令 -ENTRYPOINT ["java", "-jar", \ - "-Xms512m", "-Xmx1024m", \ - "-Djava.security.egd=file:/dev/./urandom", \ - "-Dspring.profiles.active=local", \ - "app.jar"] diff --git a/backend/gateway/deploy.sh b/backend/gateway/deploy.sh deleted file mode 100644 index a13c38b..0000000 --- a/backend/gateway/deploy.sh +++ /dev/null @@ -1,226 +0,0 @@ -#!/bin/bash - -# emotion-gateway 单独部署脚本 -# 作者: emotion-museum -# 日期: 2025-07-18 - -set -e - -# 配置变量 -SERVICE_NAME="emotion-gateway" -SERVICE_PORT="19000" -REMOTE_HOST="'root@101.200.208.45'" -REMOTE_BUILD_DIR="/data/builds" -REMOTE_DOCKER_COMPOSE_DIR="/data/docker" -PROFILE="test" -PROJECT_NAME="emotion-museum" - -# 颜色输出 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -# 日志函数 -log_info() { - echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -# 检查远程服务器连接 -check_remote_connection() { - log_info "检查远程服务器连接..." - if ssh -o ConnectTimeout=10 'root@101.200.208.45' "echo 'Connection successful'" > /dev/null 2>&1; then - log_success "远程服务器连接正常" - else - log_error "无法连接到远程服务器 'root@101.200.208.45'" - exit 1 - fi -} - -# 构建服务 -build_service() { - log_info "构建服务: $SERVICE_NAME" - - # 构建父项目依赖 - cd .. - mvn clean install -DskipTests -q - cd emotion-gateway - - # 构建当前服务 - if mvn clean package -DskipTests -Ptest -q; then - log_success "服务 $SERVICE_NAME 构建成功" - else - log_error "服务 $SERVICE_NAME 构建失败" - exit 1 - fi -} - -# 创建Dockerfile -create_dockerfile() { - log_info "创建Dockerfile: $SERVICE_NAME" - - ssh 'root@101.200.208.45' "cat > $REMOTE_DOCKER_COMPOSE_DIR/Dockerfile.${SERVICE_NAME} << 'EOF' -# 使用阿里云镜像源的OpenJDK -# 使用Java 17 Alpine镜像 -FROM openjdk:17-alpine - -WORKDIR /app - -# 安装必要的工具 (Alpine Linux使用apk) -RUN apk add --no-cache curl - -COPY ${SERVICE_NAME}-1.0.0.jar app.jar - -RUN mkdir -p /app/logs - -ENV TZ=Asia/Shanghai -RUN ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone - -EXPOSE ${SERVICE_PORT} - -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \\ - CMD curl -f http://localhost:${SERVICE_PORT}/actuator/health || exit 1 - -ENTRYPOINT [\"java\", \"-Djava.security.egd=file:/dev/./urandom\", \"-Xms512m\", \"-Xmx1024m\", \"-jar\", \"app.jar\"] -EOF" -} - -# 部署服务 -deploy_service() { - log_info "开始部署服务: $SERVICE_NAME" - - # 检查jar包 - local jar_file="target/${SERVICE_NAME}-1.0.0.jar" - if [ ! -f "$jar_file" ]; then - log_error "JAR包不存在: $jar_file" - exit 1 - fi - - # 创建远程目录 - ssh 'root@101.200.208.45' " - mkdir -p $REMOTE_BUILD_DIR - mkdir -p $REMOTE_DOCKER_COMPOSE_DIR - mkdir -p /data/logs/emotion-museum - " - - # 删除旧jar包 - log_info "删除远程旧jar包" - ssh 'root@101.200.208.45' "rm -f $REMOTE_BUILD_DIR/${SERVICE_NAME}-*.jar" - - # 上传新jar包 - log_info "上传jar包" - if scp "$jar_file" 'root@101.200.208.45':$REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar; then - log_success "jar包上传成功" - else - log_error "jar包上传失败" - exit 1 - fi - - # 创建Dockerfile - create_dockerfile - - # 停止旧容器 - log_info "停止旧容器" - ssh 'root@101.200.208.45' " - docker stop ${SERVICE_NAME} 2>/dev/null || true - docker rm ${SERVICE_NAME} 2>/dev/null || true - docker rmi ${PROJECT_NAME}/${SERVICE_NAME}:latest 2>/dev/null || true - " - - # 创建Docker网络 - ssh 'root@101.200.208.45' "docker network create emotion-network 2>/dev/null || true" - - # 构建镜像 - log_info "构建Docker镜像" - ssh 'root@101.200.208.45' " - cd $REMOTE_DOCKER_COMPOSE_DIR - # 复制jar包到Docker构建目录 - cp $REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar $REMOTE_DOCKER_COMPOSE_DIR/ - - # 构建镜像 docker build -t ${PROJECT_NAME}/${SERVICE_NAME}:latest -f Dockerfile.${SERVICE_NAME} . - - # 清理临时文件 - rm -f ${SERVICE_NAME}-1.0.0.jar " - - # 启动容器 - log_info "启动新容器" - ssh 'root@101.200.208.45' " - docker run -d \\ - --name ${SERVICE_NAME} \\ - --network emotion-network \\ - -p ${SERVICE_PORT}:${SERVICE_PORT} \\ - -v /data/logs/emotion-museum:/app/logs \\ - -e SPRING_PROFILES_ACTIVE=${PROFILE} \\ - -e MYSQL_HOST=101.200.208.45 \\ - -e MYSQL_PORT=3306 \\ - -e MYSQL_DATABASE=emotion_museum \\ - -e MYSQL_USERNAME=root \\ - -e MYSQL_PASSWORD='EmotionMuseum2025*#' \\ - -e REDIS_HOST=101.200.208.45 \\ - -e REDIS_PORT=6379 \\ - -e REDIS_PASSWORD= \\ - -e REDIS_DATABASE=0 \\ - -e NACOS_SERVER_ADDR=101.200.208.45:8848 \\ - -e NACOS_USERNAME=nacos \\ - -e NACOS_PASSWORD='Peanut2817*#' \\ - --restart unless-stopped \\ - ${PROJECT_NAME}/${SERVICE_NAME}:latest - " - - # 等待启动 - log_info "等待服务启动..." - sleep 15 - - # 检查状态 - if ssh 'root@101.200.208.45' "docker ps | grep ${SERVICE_NAME}" > /dev/null; then - log_success "服务启动成功" - - # 显示日志 - log_info "服务日志 最后20行:" - ssh 'root@101.200.208.45' "docker logs --tail 20 ${SERVICE_NAME}" - - # 健康检查 - log_info "执行健康检查..." - sleep 10 - if ssh 'root@101.200.208.45' "curl -f -s http://localhost:${SERVICE_PORT}/actuator/health" > /dev/null 2>&1; then - log_success "健康检查通过" - else - log_warning "健康检查失败,服务可能仍在启动中" - fi - else - log_error "服务启动失败" - ssh 'root@101.200.208.45' "docker logs ${SERVICE_NAME}" - exit 1 - fi -} - -# 主函数 -main() { - log_info "开始部署 $SERVICE_NAME 服务" - log_info "目标服务器: $REMOTE_HOST" - log_info "服务端口: $SERVICE_PORT" - log_info "部署环境: $PROFILE" - - check_remote_connection - build_service - deploy_service - - log_success "$SERVICE_NAME 服务部署完成!" - log_info "访问地址: http://101.200.208.45:$SERVICE_PORT" -} - -# 执行主函数 -main "$@" diff --git a/backend/gateway/pom.xml b/backend/gateway/pom.xml deleted file mode 100644 index b668c7f..0000000 --- a/backend/gateway/pom.xml +++ /dev/null @@ -1,111 +0,0 @@ - - - 4.0.0 - - - com.emotionmuseum - backend - 1.0.0 - - - gateway - gateway - API网关服务 - - - - - com.emotionmuseum - common - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-data-redis - - - - - - - org.springframework.cloud - spring-cloud-starter-gateway - - - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-discovery - - - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-config - - - - - com.alibaba.cloud - spring-cloud-starter-alibaba-sentinel - - - - - com.alibaba.cloud - spring-cloud-alibaba-sentinel-gateway - - - - - org.springframework.cloud - spring-cloud-starter-loadbalancer - - - - - org.springframework.boot - spring-boot-starter-data-redis-reactive - - - - - org.springframework.boot - spring-boot-devtools - runtime - true - - - - - org.springframework.boot - spring-boot-starter-actuator - - - - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring-boot.version} - - com.emotionmuseum.gateway.GatewayApplication - - - - - repackage - - - - - - - diff --git a/backend/gateway/src/main/java/com/emotionmuseum/gateway/GatewayApplication.java b/backend/gateway/src/main/java/com/emotionmuseum/gateway/GatewayApplication.java deleted file mode 100644 index 6bed385..0000000 --- a/backend/gateway/src/main/java/com/emotionmuseum/gateway/GatewayApplication.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.emotionmuseum.gateway; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; - -/** - * 网关服务启动类 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@SpringBootApplication -@EnableDiscoveryClient -public class GatewayApplication { - - public static void main(String[] args) { - SpringApplication.run(GatewayApplication.class, args); - } -} diff --git a/backend/gateway/src/main/java/com/emotionmuseum/gateway/config/CorsConfig.java b/backend/gateway/src/main/java/com/emotionmuseum/gateway/config/CorsConfig.java deleted file mode 100644 index a669541..0000000 --- a/backend/gateway/src/main/java/com/emotionmuseum/gateway/config/CorsConfig.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.emotionmuseum.gateway.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.reactive.CorsWebFilter; -import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; - -/** - * CORS配置 - */ -@Configuration -public class CorsConfig { - - @Bean - public CorsWebFilter corsWebFilter() { - CorsConfiguration corsConfiguration = new CorsConfiguration(); - - // 允许所有来源 - corsConfiguration.addAllowedOriginPattern("*"); - - // 允许所有请求头 - corsConfiguration.addAllowedHeader("*"); - - // 允许所有请求方法 - corsConfiguration.addAllowedMethod("*"); - - // 不允许携带凭证(这样就可以使用 * 作为来源) - corsConfiguration.setAllowCredentials(false); - - // 预检请求的缓存时间 - corsConfiguration.setMaxAge(3600L); - - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", corsConfiguration); - - return new CorsWebFilter(source); - } -} diff --git a/backend/gateway/src/main/resources/application-docker.yml b/backend/gateway/src/main/resources/application-docker.yml deleted file mode 100644 index f09d160..0000000 --- a/backend/gateway/src/main/resources/application-docker.yml +++ /dev/null @@ -1,121 +0,0 @@ -# Gateway Docker环境配置 -server: - port: 9000 - -spring: - application: - name: emotion-gateway - profiles: - active: docker - cloud: - nacos: - discovery: - server-addr: ${NACOS_SERVER_ADDR:nacos:8848} - namespace: public - group: DEFAULT_GROUP - config: - server-addr: ${NACOS_SERVER_ADDR:nacos:8848} - file-extension: yml - namespace: public - group: DEFAULT_GROUP - gateway: - discovery: - locator: - enabled: true - lower-case-service-id: true - routes: - # AI服务路由 - - id: emotion-ai - uri: lb://emotion-ai - predicates: - - Path=/api/ai/** - filters: - - StripPrefix=1 - # 用户服务路由 - - id: emotion-user - uri: lb://emotion-user - predicates: - - Path=/api/user/** - filters: - - StripPrefix=1 - # 记录服务路由 - - id: emotion-record - uri: lb://emotion-record - predicates: - - Path=/api/record/** - filters: - - StripPrefix=1 - # 成长服务路由 - - id: emotion-growth - uri: lb://emotion-growth - predicates: - - Path=/api/growth/** - filters: - - StripPrefix=1 - # 探索服务路由 - - id: emotion-explore - uri: lb://emotion-explore - predicates: - - Path=/api/explore/** - filters: - - StripPrefix=1 - # 奖励服务路由 - - id: emotion-reward - uri: lb://emotion-reward - predicates: - - Path=/api/reward/** - filters: - - StripPrefix=1 - # 统计服务路由 - - id: emotion-stats - uri: lb://emotion-stats - predicates: - - Path=/api/stats/** - filters: - - StripPrefix=1 - globalcors: - cors-configurations: - '[/**]': - allowedOriginPatterns: "*" - allowedMethods: "*" - allowedHeaders: "*" - allowCredentials: true - -# 数据源配置 -datasource: - url: jdbc:mysql://${MYSQL_HOST:mysql}:${MYSQL_PORT:3306}/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: 123456 - driver-class-name: com.mysql.cj.jdbc.Driver - -# Redis配置 -redis: - host: ${REDIS_HOST:redis} - port: ${REDIS_PORT:6379} - password: - database: 0 - timeout: 6000ms - lettuce: - pool: - max-active: 8 - max-wait: -1ms - max-idle: 8 - min-idle: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: DEBUG - org.springframework.cloud.gateway: DEBUG - pattern: - console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%logger{50}] - %msg%n" - -# 管理端点 -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus - endpoint: - health: - show-details: always diff --git a/backend/gateway/src/main/resources/application-local.yml b/backend/gateway/src/main/resources/application-local.yml deleted file mode 100644 index 46cd76f..0000000 --- a/backend/gateway/src/main/resources/application-local.yml +++ /dev/null @@ -1,143 +0,0 @@ -# 本地开发环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: localhost:8848 - namespace: - group: DEFAULT_GROUP - enabled: true - username: nacos - password: Peanut2817*# - metadata: - version: 1.0.0 - zone: local - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: localhost:8848 - namespace: - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: Peanut2817*# - - gateway: - discovery: - locator: - enabled: true - lower-case-service-id: true - - routes: - # 用户服务路由 (包含认证功能) - - id: emotion-user-route - uri: lb://emotion-user - predicates: - - Path=/user/** - filters: - - StripPrefix=0 - - # 验证码服务路由 - - id: emotion-captcha-route - uri: lb://emotion-user - predicates: - - Path=/captcha/** - filters: - - StripPrefix=0 - - # OAuth服务路由 - - id: emotion-oauth-route - uri: lb://emotion-user - predicates: - - Path=/oauth/** - filters: - - StripPrefix=0 - - # AI服务路由 - - id: emotion-ai-route - uri: lb://emotion-ai - predicates: - - Path=/ai/** - filters: - - StripPrefix=1 - - # WebSocket聊天服务路由 - - id: emotion-websocket-route - uri: lb://emotion-websocket - predicates: - - Path=/websocket/** - filters: - - StripPrefix=0 - - # WebSocket连接路由 (支持WebSocket升级) - - id: emotion-websocket-ws-route - uri: lb://emotion-websocket - predicates: - - Path=/ws/** - filters: - - StripPrefix=0 - - # 情绪记录服务路由 - - id: emotion-record-route - uri: lb://emotion-record - predicates: - - Path=/record/** - filters: - - StripPrefix=0 - - # 成长课题服务路由 - - id: emotion-growth-route - uri: lb://emotion-growth - predicates: - - Path=/growth/** - filters: - - StripPrefix=0 - - # 地图探索服务路由 - - id: emotion-explore-route - uri: lb://emotion-explore - predicates: - - Path=/explore/** - filters: - - StripPrefix=0 - - # 成就奖励服务路由 - - id: emotion-reward-route - uri: lb://emotion-reward - predicates: - - Path=/reward/** - filters: - - StripPrefix=0 - - # 统计分析服务路由 - - id: emotion-stats-route - uri: lb://emotion-stats - predicates: - - Path=/stats/** - filters: - - StripPrefix=0 - - # Redis配置 - data: - redis: - host: localhost - port: 6379 - password: - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: debug - org.springframework.cloud.gateway: debug - org.springframework.web: debug - file: - name: logs/emotion-gateway-local.log \ No newline at end of file diff --git a/backend/gateway/src/main/resources/application-prod.yml b/backend/gateway/src/main/resources/application-prod.yml deleted file mode 100644 index 822e41e..0000000 --- a/backend/gateway/src/main/resources/application-prod.yml +++ /dev/null @@ -1,144 +0,0 @@ -# 生产环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: prod - group: DEFAULT_GROUP - enabled: true - username: nacos - password: EmotionMuseum2025 - metadata: - version: 1.0.0 - zone: prod - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: 101.200.208.45:8848 - namespace: prod - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: EmotionMuseum2025 - - gateway: - discovery: - locator: - enabled: true - lower-case-service-id: true - - routes: - # 用户服务路由 (包含认证功能) - - id: emotion-user-route - uri: lb://emotion-user - predicates: - - Path=/user/** - filters: - - StripPrefix=0 - - # 验证码服务路由 - - id: emotion-captcha-route - uri: lb://emotion-user - predicates: - - Path=/captcha/** - filters: - - StripPrefix=0 - - # OAuth服务路由 - - id: emotion-oauth-route - uri: lb://emotion-user - predicates: - - Path=/oauth/** - filters: - - StripPrefix=0 - - # AI服务路由 - - id: emotion-ai-route - uri: lb://emotion-ai - predicates: - - Path=/ai/** - filters: - - StripPrefix=1 - - # WebSocket聊天服务路由 - - id: emotion-websocket-route - uri: lb://emotion-websocket - predicates: - - Path=/websocket/** - filters: - - StripPrefix=0 - - # WebSocket连接路由 (支持WebSocket升级) - - id: emotion-websocket-ws-route - uri: lb://emotion-websocket - predicates: - - Path=/ws/** - filters: - - StripPrefix=0 - - # 情绪记录服务路由 - - id: emotion-record-route - uri: lb://emotion-record - predicates: - - Path=/record/** - filters: - - StripPrefix=0 - - # 成长课题服务路由 - - id: emotion-growth-route - uri: lb://emotion-growth - predicates: - - Path=/growth/** - filters: - - StripPrefix=0 - - # 地图探索服务路由 - - id: emotion-explore-route - uri: lb://emotion-explore - predicates: - - Path=/explore/** - filters: - - StripPrefix=0 - - # 成就奖励服务路由 - - id: emotion-reward-route - uri: lb://emotion-reward - predicates: - - Path=/reward/** - filters: - - StripPrefix=0 - - # 统计分析服务路由 - - id: emotion-stats-route - uri: lb://emotion-stats - predicates: - - Path=/stats/** - filters: - - StripPrefix=0 - - # Redis配置 - data: - redis: - host: 101.200.208.45 - port: 6379 - password: EmotionMuseum2025*# - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: warn - org.springframework.cloud.gateway: warn - org.springframework.web: warn - com.alibaba.nacos: error - file: - name: logs/emotion-gateway-prod.log diff --git a/backend/gateway/src/main/resources/application-test.yml b/backend/gateway/src/main/resources/application-test.yml deleted file mode 100644 index 84edd11..0000000 --- a/backend/gateway/src/main/resources/application-test.yml +++ /dev/null @@ -1,144 +0,0 @@ -# 测试环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: test - group: DEFAULT_GROUP - enabled: true - username: nacos - password: EmotionMuseum2025 - metadata: - version: 1.0.0 - zone: test - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: 101.200.208.45:8848 - namespace: test - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: EmotionMuseum2025 - - gateway: - discovery: - locator: - enabled: true - lower-case-service-id: true - - routes: - # 用户服务路由 (包含认证功能) - - id: emotion-user-route - uri: lb://emotion-user - predicates: - - Path=/user/** - filters: - - StripPrefix=0 - - # 验证码服务路由 - - id: emotion-captcha-route - uri: lb://emotion-user - predicates: - - Path=/captcha/** - filters: - - StripPrefix=0 - - # OAuth服务路由 - - id: emotion-oauth-route - uri: lb://emotion-user - predicates: - - Path=/oauth/** - filters: - - StripPrefix=0 - - # AI服务路由 - - id: emotion-ai-route - uri: lb://emotion-ai - predicates: - - Path=/ai/** - filters: - - StripPrefix=1 - - # WebSocket聊天服务路由 - - id: emotion-websocket-route - uri: lb://emotion-websocket - predicates: - - Path=/websocket/** - filters: - - StripPrefix=0 - - # WebSocket连接路由 (支持WebSocket升级) - - id: emotion-websocket-ws-route - uri: lb://emotion-websocket - predicates: - - Path=/ws/** - filters: - - StripPrefix=0 - - # 情绪记录服务路由 - - id: emotion-record-route - uri: lb://emotion-record - predicates: - - Path=/record/** - filters: - - StripPrefix=0 - - # 成长课题服务路由 - - id: emotion-growth-route - uri: lb://emotion-growth - predicates: - - Path=/growth/** - filters: - - StripPrefix=0 - - # 地图探索服务路由 - - id: emotion-explore-route - uri: lb://emotion-explore - predicates: - - Path=/explore/** - filters: - - StripPrefix=0 - - # 成就奖励服务路由 - - id: emotion-reward-route - uri: lb://emotion-reward - predicates: - - Path=/reward/** - filters: - - StripPrefix=0 - - # 统计分析服务路由 - - id: emotion-stats-route - uri: lb://emotion-stats - predicates: - - Path=/stats/** - filters: - - StripPrefix=0 - - # Redis配置 - data: - redis: - host: 101.200.208.45 - port: 6379 - password: EmotionMuseum2025*# - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: info - org.springframework.cloud.gateway: info - org.springframework.web: info - com.alibaba.nacos: warn - file: - name: logs/emotion-gateway-test.log diff --git a/backend/gateway/src/main/resources/application.yml b/backend/gateway/src/main/resources/application.yml deleted file mode 100644 index e9dca6f..0000000 --- a/backend/gateway/src/main/resources/application.yml +++ /dev/null @@ -1,242 +0,0 @@ -server: - port: 19000 - -spring: - application: - name: emotion-gateway - - # 配置文件激活 - profiles: - active: ${SPRING_PROFILES_ACTIVE:local} - - # 允许Bean覆盖和循环引用 - main: - allow-bean-definition-overriding: true - allow-circular-references: true - - # 排除数据库自动配置 - autoconfigure: - exclude: - - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration - - org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration - - com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration - - # Redis配置 - data: - redis: - host: ${REDIS_HOST:localhost} - port: ${REDIS_PORT:6379} - password: ${REDIS_PASSWORD:} - database: 0 - timeout: 10000ms - lettuce: - pool: - max-active: 8 - max-wait: -1ms - max-idle: 8 - min-idle: 0 - # 网关和Nacos配置 - cloud: - nacos: - discovery: - server-addr: ${NACOS_HOST:localhost}:${NACOS_PORT:8848} - namespace: ${NACOS_NAMESPACE:} - group: ${NACOS_GROUP:DEFAULT_GROUP} - enabled: ${NACOS_DISCOVERY_ENABLED:true} - username: ${NACOS_USERNAME:nacos} - password: ${NACOS_PASSWORD:nacos} - metadata: - version: 1.0.0 - zone: ${NACOS_ZONE:default} - config: - server-addr: ${NACOS_HOST:localhost}:${NACOS_PORT:8848} - namespace: ${NACOS_NAMESPACE:} - group: ${NACOS_GROUP:DEFAULT_GROUP} - file-extension: yml - enabled: ${NACOS_CONFIG_ENABLED:false} - username: ${NACOS_USERNAME:nacos} - password: ${NACOS_PASSWORD:nacos} - gateway: - discovery: - locator: - enabled: true - lower-case-service-id: true - routes: - # 认证服务路由 - API路径 - - id: emotion-auth-api - uri: lb://emotion-auth - predicates: - - Path=/api/auth/** - filters: - - StripPrefix=1 - - # 认证服务路由 - 直接路径 - - id: emotion-auth-direct - uri: lb://emotion-auth - predicates: - - Path=/auth/** - filters: - - StripPrefix=1 - - # 用户服务路由 - API路径 - - id: emotion-user-api - uri: lb://emotion-user - predicates: - - Path=/api/user/** - filters: - - StripPrefix=2 - - # 用户服务路由 - 直接路径 - - id: emotion-user-direct - uri: lb://emotion-user - predicates: - - Path=/user/** - filters: - - StripPrefix=1 - - # AI对话服务路由 - API路径 - - id: emotion-ai-api - uri: lb://emotion-ai - predicates: - - Path=/api/ai/** - filters: - - StripPrefix=2 - - # AI对话服务路由 - 直接路径 - - id: emotion-ai-direct - uri: lb://emotion-ai - predicates: - - Path=/ai/** - filters: - - StripPrefix=1 - - # WebSocket聊天服务路由 - API路径 - - id: emotion-websocket-api - uri: lb://emotion-websocket - predicates: - - Path=/api/websocket/** - filters: - - StripPrefix=2 - - # WebSocket聊天服务路由 - 直接路径 - - id: emotion-websocket-direct - uri: lb://emotion-websocket - predicates: - - Path=/websocket/** - filters: - - StripPrefix=1 - - # 情绪记录服务路由 - API路径 - - id: emotion-record-api - uri: lb://emotion-record - predicates: - - Path=/api/record/** - filters: - - StripPrefix=2 - - # 情绪记录服务路由 - 直接路径 - - id: emotion-record-direct - uri: lb://emotion-record - predicates: - - Path=/record/** - filters: - - StripPrefix=1 - - # 成长课题服务路由 - API路径 - - id: emotion-growth-api - uri: lb://emotion-growth - predicates: - - Path=/api/growth/** - filters: - - StripPrefix=2 - - # 成长课题服务路由 - 直接路径 - - id: emotion-growth-direct - uri: lb://emotion-growth - predicates: - - Path=/growth/** - filters: - - StripPrefix=1 - - # 地图探索服务路由 - API路径 - - id: emotion-explore-api - uri: lb://emotion-explore - predicates: - - Path=/api/explore/** - filters: - - StripPrefix=2 - - # 地图探索服务路由 - 直接路径 - - id: emotion-explore-direct - uri: lb://emotion-explore - predicates: - - Path=/explore/** - filters: - - StripPrefix=1 - - # 成就奖励服务路由 - API路径 - - id: emotion-reward-api - uri: lb://emotion-reward - predicates: - - Path=/api/reward/** - filters: - - StripPrefix=2 - - # 成就奖励服务路由 - 直接路径 - - id: emotion-reward-direct - uri: lb://emotion-reward - predicates: - - Path=/reward/** - filters: - - StripPrefix=1 - - # 统计分析服务路由 - API路径 - - id: emotion-stats-api - uri: lb://emotion-stats - predicates: - - Path=/api/stats/** - filters: - - StripPrefix=2 - - # 统计分析服务路由 - 直接路径 - - id: emotion-stats-direct - uri: lb://emotion-stats - predicates: - - Path=/stats/** - filters: - - StripPrefix=1 - - # 验证码服务路由 (通过认证服务) - - id: captcha-service - uri: lb://emotion-auth - predicates: - - Path=/captcha/** - filters: - - StripPrefix=1 - - - - # 全局过滤器 (暂时禁用,需要实现对应的过滤器类) - # default-filters: - # - name: GlobalAuthFilter - # - name: GlobalLogFilter - -# 监控配置 -management: - endpoints: - web: - exposure: - include: health,info,gateway - endpoint: - health: - show-details: always - -# 日志配置 -logging: - file: - path: /data/logs/emotion-museum/gateway - level: - com.emotionmuseum: debug - org.springframework.cloud.gateway: debug - pattern: - console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%logger{50}] - %msg%n" diff --git a/backend/gateway/test-gateway-routes.sh b/backend/gateway/test-gateway-routes.sh deleted file mode 100644 index 551bc67..0000000 --- a/backend/gateway/test-gateway-routes.sh +++ /dev/null @@ -1,157 +0,0 @@ -#!/bin/bash - -# ============================================================================ -# 网关路由测试脚本 -# 测试所有微服务通过网关的路由转发功能 -# ============================================================================ - -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# 网关地址 -GATEWAY_URL="http://localhost:19000" - -echo -e "${BLUE}===========================================${NC}" -echo -e "${BLUE}网关路由测试${NC}" -echo -e "${BLUE}===========================================${NC}" - -# 测试函数 -test_route() { - local service_name=$1 - local route_path=$2 - local expected_port=$3 - local description=$4 - - echo -e "${YELLOW}测试 $description ($service_name)${NC}" - echo -e "路由: $GATEWAY_URL$route_path" - echo -e "期望转发到: localhost:$expected_port" - - # 测试健康检查端点 - local health_url="$GATEWAY_URL$route_path/actuator/health" - - response=$(curl -s -w "%{http_code}" -o /tmp/gateway_test_response "$health_url" 2>/dev/null) - http_code="${response: -3}" - - if [ "$http_code" = "200" ]; then - echo -e "${GREEN}✅ 路由正常 (HTTP $http_code)${NC}" - # 显示响应内容 - if [ -f /tmp/gateway_test_response ]; then - response_body=$(cat /tmp/gateway_test_response) - echo -e "${GREEN}响应: $response_body${NC}" - fi - elif [ "$http_code" = "404" ]; then - echo -e "${YELLOW}⚠️ 服务未启动或路径不存在 (HTTP $http_code)${NC}" - elif [ "$http_code" = "000" ]; then - echo -e "${RED}❌ 连接失败 - 网关可能未启动${NC}" - else - echo -e "${RED}❌ 路由异常 (HTTP $http_code)${NC}" - if [ -f /tmp/gateway_test_response ]; then - response_body=$(cat /tmp/gateway_test_response) - echo -e "${RED}错误响应: $response_body${NC}" - fi - fi - echo "" -} - -# 首先测试网关本身 -echo -e "${YELLOW}测试网关服务本身${NC}" -gateway_response=$(curl -s -w "%{http_code}" -o /tmp/gateway_health "$GATEWAY_URL/actuator/health" 2>/dev/null) -gateway_code="${gateway_response: -3}" - -if [ "$gateway_code" = "200" ]; then - echo -e "${GREEN}✅ 网关服务正常运行${NC}" - if [ -f /tmp/gateway_health ]; then - gateway_health=$(cat /tmp/gateway_health) - echo -e "${GREEN}网关状态: $gateway_health${NC}" - fi -else - echo -e "${RED}❌ 网关服务未启动或异常 (HTTP $gateway_code)${NC}" - echo -e "${RED}请先启动网关服务: cd backend/emotion-gateway && mvn spring-boot:run${NC}" - exit 1 -fi -echo "" - -# 测试各个服务路由 -echo -e "${BLUE}开始测试各服务路由...${NC}" -echo "" - -# 用户服务路由 -test_route "emotion-user" "/user" "19001" "用户服务" - -# AI服务路由 -test_route "emotion-ai" "/ai" "19002" "AI对话服务" - -# WebSocket服务路由 -test_route "emotion-websocket" "/websocket" "19007" "WebSocket聊天服务" - -# 情绪记录服务路由 -test_route "emotion-record" "/record" "19003" "情绪记录服务" - -# 成长课题服务路由 -test_route "emotion-growth" "/growth" "19004" "成长课题服务" - -# 地图探索服务路由 -test_route "emotion-explore" "/explore" "19005" "地图探索服务" - -# 成就奖励服务路由 -test_route "emotion-reward" "/reward" "19006" "成就奖励服务" - -# 统计分析服务路由 -test_route "emotion-stats" "/stats" "19008" "统计分析服务" - -echo -e "${BLUE}===========================================${NC}" - -# 测试特殊路由 -echo -e "${YELLOW}测试特殊功能路由...${NC}" -echo "" - -# 测试验证码路由 -echo -e "${YELLOW}测试验证码服务路由${NC}" -captcha_url="$GATEWAY_URL/captcha/api/captcha/generate" -echo -e "URL: $captcha_url" -captcha_response=$(curl -s -w "%{http_code}" -o /tmp/captcha_test "$captcha_url" 2>/dev/null) -captcha_code="${captcha_response: -3}" -if [ "$captcha_code" = "200" ] || [ "$captcha_code" = "404" ]; then - echo -e "${GREEN}✅ 验证码路由转发正常${NC}" -else - echo -e "${RED}❌ 验证码路由异常 (HTTP $captcha_code)${NC}" -fi -echo "" - -# 测试WebSocket特殊接口 -echo -e "${YELLOW}测试WebSocket在线用户接口${NC}" -ws_users_url="$GATEWAY_URL/websocket/online-users" -echo -e "URL: $ws_users_url" -ws_response=$(curl -s -w "%{http_code}" -o /tmp/ws_test "$ws_users_url" 2>/dev/null) -ws_code="${ws_response: -3}" -if [ "$ws_code" = "200" ]; then - echo -e "${GREEN}✅ WebSocket REST API路由正常${NC}" - if [ -f /tmp/ws_test ]; then - ws_body=$(cat /tmp/ws_test) - echo -e "${GREEN}响应: $ws_body${NC}" - fi -elif [ "$ws_code" = "404" ]; then - echo -e "${YELLOW}⚠️ WebSocket服务未启动${NC}" -else - echo -e "${RED}❌ WebSocket路由异常 (HTTP $ws_code)${NC}" -fi -echo "" - -echo -e "${BLUE}===========================================${NC}" -echo -e "${BLUE}测试完成${NC}" -echo -e "${BLUE}===========================================${NC}" - -# 清理临时文件 -rm -f /tmp/gateway_test_response /tmp/gateway_health /tmp/captcha_test /tmp/ws_test - -echo -e "${YELLOW}提示:${NC}" -echo -e "1. 如果某些服务显示未启动,请使用以下命令启动:" -echo -e " ${GREEN}cd backend && ./start-services.sh${NC}" -echo -e "2. 单独启动某个服务:" -echo -e " ${GREEN}cd backend/emotion-xxx && mvn spring-boot:run -Dspring-boot.run.profiles=local${NC}" -echo -e "3. 查看网关日志:" -echo -e " ${GREEN}tail -f backend/logs/emotion-gateway-local.log${NC}" diff --git a/backend/gateway/网关配置更新总结.md b/backend/gateway/网关配置更新总结.md deleted file mode 100644 index f053422..0000000 --- a/backend/gateway/网关配置更新总结.md +++ /dev/null @@ -1,181 +0,0 @@ -# 网关服务配置更新总结 - -## 更新时间 -2025-07-17 - -## 更新内容 - -### 1. 服务端口配置更新 - -根据当前backend下的最新模块,更新了所有微服务的路由配置: - -| 服务名称 | 端口 | 路由路径 | 描述 | -|---------|------|----------|------| -| emotion-gateway | 19000 | - | 网关服务 | -| emotion-user | 19001 | /user/**, /captcha/**, /oauth/** | 用户服务(包含认证) | -| emotion-ai | 19002 | /ai/** | AI对话服务 | -| emotion-record | 19003 | /record/** | 情绪记录服务 | -| emotion-growth | 19004 | /growth/** | 成长课题服务 | -| emotion-explore | 19005 | /explore/** | 地图探索服务 | -| emotion-reward | 19006 | /reward/** | 成就奖励服务 | -| emotion-websocket | 19007 | /websocket/**, /ws/** | WebSocket聊天服务 | -| emotion-stats | 19008 | /stats/** | 统计分析服务 | - -### 2. 新增WebSocket支持 - -#### WebSocket路由配置 -```yaml -# WebSocket聊天服务路由 -- id: emotion-websocket-route - uri: http://localhost:19007 - predicates: - - Path=/websocket/** - filters: - - StripPrefix=0 - -# WebSocket连接路由 (支持WebSocket升级) -- id: emotion-websocket-ws-route - uri: ws://localhost:19007 - predicates: - - Path=/ws/** - filters: - - StripPrefix=0 -``` - -#### 特性 -- 支持HTTP和WebSocket协议 -- REST API路径:`/websocket/**` -- WebSocket连接路径:`/ws/**` -- 自动协议升级支持 - -### 3. 新增跨域配置 - -```yaml -# 全局跨域配置 -globalcors: - cors-configurations: - '[/**]': - allowed-origins: "*" - allowed-methods: "*" - allowed-headers: "*" - allow-credentials: true -``` - -### 4. 端口冲突解决 - -**问题**:emotion-stats和emotion-websocket都使用19007端口 - -**解决方案**: -- emotion-websocket保持19007端口(新增的重要服务) -- emotion-stats更改为19008端口 - -**修改文件**: -- `backend/emotion-stats/src/main/resources/application.yml` - -### 5. 完整路由列表 - -#### 用户相关服务 -- `/user/**` → emotion-user:19001 -- `/captcha/**` → emotion-user:19001 -- `/oauth/**` → emotion-user:19001 - -#### AI相关服务 -- `/ai/**` → emotion-ai:19002 -- `/websocket/**` → emotion-websocket:19007 (REST API) -- `/ws/**` → emotion-websocket:19007 (WebSocket) - -#### 业务功能服务 -- `/record/**` → emotion-record:19003 -- `/growth/**` → emotion-growth:19004 -- `/explore/**` → emotion-explore:19005 -- `/reward/**` → emotion-reward:19006 -- `/stats/**` → emotion-stats:19008 - -## 使用示例 - -### 1. 通过网关访问AI服务 -```bash -# 直接访问 -curl http://localhost:19002/api/ai/chat/send - -# 通过网关访问 -curl http://localhost:19000/ai/api/ai/chat/send -``` - -### 2. 通过网关访问WebSocket服务 -```bash -# REST API测试 -curl http://localhost:19000/websocket/online-users - -# WebSocket连接 -ws://localhost:19000/ws/chat -``` - -### 3. 通过网关访问用户服务 -```bash -# 用户注册 -curl http://localhost:19000/user/api/user/register - -# 获取验证码 -curl http://localhost:19000/captcha/api/captcha/generate -``` - -## 配置文件位置 - -- **主配置**:`backend/emotion-gateway/src/main/resources/application-local.yml` -- **生产配置**:`backend/emotion-gateway/src/main/resources/application-prod.yml` -- **Docker配置**:`backend/emotion-gateway/src/main/resources/application-docker.yml` - -## 注意事项 - -1. **端口一致性**:确保各服务的实际端口与网关配置一致 -2. **WebSocket支持**:新增的WebSocket路由支持协议升级 -3. **跨域配置**:已添加全局跨域支持,适用于前端开发 -4. **路径匹配**:使用`StripPrefix=0`保持原始路径 -5. **服务发现**:本地环境禁用Nacos,使用直连方式 - -## 验证方法 - -### 1. 检查网关状态 -```bash -curl http://localhost:19000/actuator/health -``` - -### 2. 测试路由转发 -```bash -# 测试用户服务路由 -curl http://localhost:19000/user/actuator/health - -# 测试AI服务路由 -curl http://localhost:19000/ai/actuator/health - -# 测试WebSocket服务路由 -curl http://localhost:19000/websocket/online-users -``` - -### 3. 查看网关日志 -```bash -tail -f backend/logs/emotion-gateway-local.log -``` - -## 后续优化建议 - -1. **负载均衡**:生产环境可考虑启用Nacos服务发现 -2. **限流配置**:添加请求限流和熔断机制 -3. **安全配置**:添加JWT认证过滤器 -4. **监控配置**:集成链路追踪和指标监控 -5. **缓存配置**:添加响应缓存机制 - -## 相关文件更新 - -- ✅ `backend/emotion-gateway/src/main/resources/application-local.yml` -- ✅ `backend/emotion-stats/src/main/resources/application.yml` -- ✅ 网关配置文档更新 - -## 测试状态 - -- ✅ 配置文件语法正确 -- ✅ 端口冲突已解决 -- ✅ WebSocket路由已添加 -- ✅ 跨域配置已添加 -- ⏳ 实际路由转发测试待进行 diff --git a/backend/growth/Dockerfile b/backend/growth/Dockerfile deleted file mode 100644 index 73d36aa..0000000 --- a/backend/growth/Dockerfile +++ /dev/null @@ -1,48 +0,0 @@ -# 成长服务Dockerfile -FROM openjdk:17-jdk-alpine - -# 设置工作目录 -WORKDIR /app - -# 安装必要的工具 -RUN apk add --no-cache curl tzdata && \ - cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ - echo "Asia/Shanghai" > /etc/timezone - -# 复制Maven构建文件 -COPY pom.xml ./ -COPY emotion-common ./emotion-common -COPY emotion-growth ./emotion-growth - -# 安装Maven -RUN apk add --no-cache maven - -# 构建应用 -RUN mvn clean package -DskipTests -pl emotion-growth -am - -# 创建运行用户 -RUN addgroup -g 1000 emotion && \ - adduser -D -s /bin/sh -u 1000 -G emotion emotion - -# 复制jar文件 -RUN cp emotion-growth/target/emotion-growth-*.jar app.jar - -# 设置文件权限 -RUN chown -R emotion:emotion /app - -# 切换到非root用户 -USER emotion - -# 健康检查 -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ - CMD curl -f http://localhost:19004/actuator/health || exit 1 - -# 暴露端口 -EXPOSE 19004 - -# 启动命令 -ENTRYPOINT ["java", "-jar", \ - "-Xms512m", "-Xmx1024m", \ - "-Djava.security.egd=file:/dev/./urandom", \ - "-Dspring.profiles.active=local", \ - "app.jar"] diff --git a/backend/growth/api/pom.xml b/backend/growth/api/pom.xml deleted file mode 100644 index d25fd96..0000000 --- a/backend/growth/api/pom.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - growth - 1.0.0 - ../pom.xml - - growth-api - growth-api - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - com.emotionmuseum - common - ${project.version} - - - diff --git a/backend/growth/api/src/main/java/com/emotionmuseum/growth/api/client/GrowthClient.java b/backend/growth/api/src/main/java/com/emotionmuseum/growth/api/client/GrowthClient.java deleted file mode 100644 index 4cd8752..0000000 --- a/backend/growth/api/src/main/java/com/emotionmuseum/growth/api/client/GrowthClient.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.emotionmuseum.growth.api.client; - -import com.emotionmuseum.common.result.Result; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; - -@FeignClient(name = "growth") -public interface GrowthClient { - - @GetMapping("/growth/health") - Result health(); -} - - diff --git a/backend/growth/deploy.sh b/backend/growth/deploy.sh deleted file mode 100644 index a3b4fbe..0000000 --- a/backend/growth/deploy.sh +++ /dev/null @@ -1,226 +0,0 @@ -#!/bin/bash - -# emotion-growth 单独部署脚本 -# 作者: emotion-museum -# 日期: 2025-07-18 - -set -e - -# 配置变量 -SERVICE_NAME="emotion-growth" -SERVICE_PORT="19004" -REMOTE_HOST="'root@101.200.208.45'" -REMOTE_BUILD_DIR="/data/builds" -REMOTE_DOCKER_COMPOSE_DIR="/data/docker" -PROFILE="test" -PROJECT_NAME="emotion-museum" - -# 颜色输出 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -# 日志函数 -log_info() { - echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -# 检查远程服务器连接 -check_remote_connection() { - log_info "检查远程服务器连接..." - if ssh -o ConnectTimeout=10 'root@101.200.208.45' "echo 'Connection successful'" > /dev/null 2>&1; then - log_success "远程服务器连接正常" - else - log_error "无法连接到远程服务器 'root@101.200.208.45'" - exit 1 - fi -} - -# 构建服务 -build_service() { - log_info "构建服务: $SERVICE_NAME" - - # 构建父项目依赖 - cd .. - mvn clean install -DskipTests -q - cd emotion-growth - - # 构建当前服务 - if mvn clean package -DskipTests -Ptest -q; then - log_success "服务 $SERVICE_NAME 构建成功" - else - log_error "服务 $SERVICE_NAME 构建失败" - exit 1 - fi -} - -# 创建Dockerfile -create_dockerfile() { - log_info "创建Dockerfile: $SERVICE_NAME" - - ssh 'root@101.200.208.45' "cat > $REMOTE_DOCKER_COMPOSE_DIR/Dockerfile.${SERVICE_NAME} << 'EOF' -# 使用阿里云镜像源的OpenJDK -# 使用Java 17 Alpine镜像 -FROM openjdk:17-alpine - -WORKDIR /app - -# 安装必要的工具 (Alpine Linux使用apk) -RUN apk add --no-cache curl - -COPY ${SERVICE_NAME}-1.0.0.jar app.jar - -RUN mkdir -p /app/logs - -ENV TZ=Asia/Shanghai -RUN ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone - -EXPOSE ${SERVICE_PORT} - -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \\ - CMD curl -f http://localhost:${SERVICE_PORT}/actuator/health || exit 1 - -ENTRYPOINT [\"java\", \"-Djava.security.egd=file:/dev/./urandom\", \"-Xms512m\", \"-Xmx1024m\", \"-jar\", \"app.jar\"] -EOF" -} - -# 部署服务 -deploy_service() { - log_info "开始部署服务: $SERVICE_NAME" - - # 检查jar包 - local jar_file="target/${SERVICE_NAME}-1.0.0.jar" - if [ ! -f "$jar_file" ]; then - log_error "JAR包不存在: $jar_file" - exit 1 - fi - - # 创建远程目录 - ssh 'root@101.200.208.45' " - mkdir -p $REMOTE_BUILD_DIR - mkdir -p $REMOTE_DOCKER_COMPOSE_DIR - mkdir -p /data/logs/emotion-museum - " - - # 删除旧jar包 - log_info "删除远程旧jar包" - ssh 'root@101.200.208.45' "rm -f $REMOTE_BUILD_DIR/${SERVICE_NAME}-*.jar" - - # 上传新jar包 - log_info "上传jar包" - if scp "$jar_file" 'root@101.200.208.45':$REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar; then - log_success "jar包上传成功" - else - log_error "jar包上传失败" - exit 1 - fi - - # 创建Dockerfile - create_dockerfile - - # 停止旧容器 - log_info "停止旧容器" - ssh 'root@101.200.208.45' " - docker stop ${SERVICE_NAME} 2>/dev/null || true - docker rm ${SERVICE_NAME} 2>/dev/null || true - docker rmi ${PROJECT_NAME}/${SERVICE_NAME}:latest 2>/dev/null || true - " - - # 创建Docker网络 - ssh 'root@101.200.208.45' "docker network create emotion-network 2>/dev/null || true" - - # 构建镜像 - log_info "构建Docker镜像" - ssh 'root@101.200.208.45' " - cd $REMOTE_DOCKER_COMPOSE_DIR - # 复制jar包到Docker构建目录 - cp $REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar $REMOTE_DOCKER_COMPOSE_DIR/ - - # 构建镜像 docker build -t ${PROJECT_NAME}/${SERVICE_NAME}:latest -f Dockerfile.${SERVICE_NAME} . - - # 清理临时文件 - rm -f ${SERVICE_NAME}-1.0.0.jar " - - # 启动容器 - log_info "启动新容器" - ssh 'root@101.200.208.45' " - docker run -d \\ - --name ${SERVICE_NAME} \\ - --network emotion-network \\ - -p ${SERVICE_PORT}:${SERVICE_PORT} \\ - -v /data/logs/emotion-museum:/app/logs \\ - -e SPRING_PROFILES_ACTIVE=${PROFILE} \\ - -e MYSQL_HOST=101.200.208.45 \\ - -e MYSQL_PORT=3306 \\ - -e MYSQL_DATABASE=emotion_museum \\ - -e MYSQL_USERNAME=root \\ - -e MYSQL_PASSWORD='EmotionMuseum2025*#' \\ - -e REDIS_HOST=101.200.208.45 \\ - -e REDIS_PORT=6379 \\ - -e REDIS_PASSWORD= \\ - -e REDIS_DATABASE=0 \\ - -e NACOS_SERVER_ADDR=101.200.208.45:8848 \\ - -e NACOS_USERNAME=nacos \\ - -e NACOS_PASSWORD='Peanut2817*#' \\ - --restart unless-stopped \\ - ${PROJECT_NAME}/${SERVICE_NAME}:latest - " - - # 等待启动 - log_info "等待服务启动..." - sleep 15 - - # 检查状态 - if ssh 'root@101.200.208.45' "docker ps | grep ${SERVICE_NAME}" > /dev/null; then - log_success "服务启动成功" - - # 显示日志 - log_info "服务日志 最后20行:" - ssh 'root@101.200.208.45' "docker logs --tail 20 ${SERVICE_NAME}" - - # 健康检查 - log_info "执行健康检查..." - sleep 10 - if ssh 'root@101.200.208.45' "curl -f -s http://localhost:${SERVICE_PORT}/actuator/health" > /dev/null 2>&1; then - log_success "健康检查通过" - else - log_warning "健康检查失败,服务可能仍在启动中" - fi - else - log_error "服务启动失败" - ssh 'root@101.200.208.45' "docker logs ${SERVICE_NAME}" - exit 1 - fi -} - -# 主函数 -main() { - log_info "开始部署 $SERVICE_NAME 服务" - log_info "目标服务器: $REMOTE_HOST" - log_info "服务端口: $SERVICE_PORT" - log_info "部署环境: $PROFILE" - - check_remote_connection - build_service - deploy_service - - log_success "$SERVICE_NAME 服务部署完成!" - log_info "访问地址: http://101.200.208.45:$SERVICE_PORT" -} - -# 执行主函数 -main "$@" diff --git a/backend/growth/pom.xml b/backend/growth/pom.xml deleted file mode 100644 index f358116..0000000 --- a/backend/growth/pom.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - backend - 1.0.0 - ../pom.xml - - growth - pom - - api - server - - diff --git a/backend/growth/server/pom.xml b/backend/growth/server/pom.xml deleted file mode 100644 index 9d54648..0000000 --- a/backend/growth/server/pom.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - 4.0.0 - - - com.emotionmuseum - growth - 1.0.0 - - - growth-server - growth - 成长课题服务 - - - - com.emotionmuseum - growth-api - ${project.version} - - - com.emotionmuseum - common - ${project.version} - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-validation - - - com.baomidou - mybatis-plus-boot-starter - - - mysql - mysql-connector-java - - - com.alibaba - druid-spring-boot-starter - - - org.springframework.boot - spring-boot-starter-data-redis - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-discovery - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-config - - - org.springframework.boot - spring-boot-starter-actuator - - - \ No newline at end of file diff --git a/backend/growth/server/src/main/java/com/emotionmuseum/growth/GrowthApplication.java b/backend/growth/server/src/main/java/com/emotionmuseum/growth/GrowthApplication.java deleted file mode 100644 index 0b70b4e..0000000 --- a/backend/growth/server/src/main/java/com/emotionmuseum/growth/GrowthApplication.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.emotionmuseum.growth; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - - -/** - * 成长课题服务启动类 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@SpringBootApplication -@MapperScan("com.emotionmuseum.growth.mapper") -public class GrowthApplication { - - public static void main(String[] args) { - SpringApplication.run(GrowthApplication.class, args); - } -} diff --git a/backend/growth/server/src/main/java/com/emotionmuseum/growth/entity/GrowthTopic.java b/backend/growth/server/src/main/java/com/emotionmuseum/growth/entity/GrowthTopic.java deleted file mode 100644 index ab6a693..0000000 --- a/backend/growth/server/src/main/java/com/emotionmuseum/growth/entity/GrowthTopic.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.emotionmuseum.growth.entity; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import com.emotionmuseum.common.entity.BaseEntity; -import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.Map; - -/** - * 成长课题实体 - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName(value = "growth_topic", autoResultMap = true) -public class GrowthTopic extends BaseEntity { - - /** - * 课题标题 - */ - @TableField("title") - private String title; - - /** - * 分类 - */ - @TableField("category") - private String category; - - /** - * 难度: easy-简单, medium-中等, hard-困难 - */ - @TableField("difficulty") - private String difficulty; - - /** - * 描述 - */ - @TableField("description") - private String description; - - /** - * 内容 - */ - @TableField("content") - private String content; - - /** - * 持续天数 - */ - @TableField("duration_days") - private Integer durationDays; - - /** - * 解锁条件 - */ - @TableField(value = "unlock_conditions", typeHandler = JacksonTypeHandler.class) - private Map unlockConditions; - - /** - * 是否解锁: 0-未解锁, 1-已解锁 - */ - @TableField("is_unlocked") - private Integer isUnlocked; - - /** - * 进度百分比 (0.00-100.00) - */ - @TableField("progress") - private BigDecimal progress; - - /** - * 完成时间 - */ - @TableField("completed_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime completedTime; - - /** - * 奖励 - */ - @TableField(value = "rewards", typeHandler = JacksonTypeHandler.class) - private Map rewards; -} diff --git a/backend/growth/server/src/main/java/com/emotionmuseum/growth/entity/TopicInteraction.java b/backend/growth/server/src/main/java/com/emotionmuseum/growth/entity/TopicInteraction.java deleted file mode 100644 index 7debb97..0000000 --- a/backend/growth/server/src/main/java/com/emotionmuseum/growth/entity/TopicInteraction.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.emotionmuseum.growth.entity; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import com.emotionmuseum.common.entity.BaseEntity; -import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.time.LocalDateTime; - -/** - * 课题互动实体 - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName("topic_interaction") -public class TopicInteraction extends BaseEntity { - - /** - * 课题ID - */ - @TableField("topic_id") - private String topicId; - - /** - * 互动类型 - */ - @TableField("type") - private String type; - - /** - * 内容 - */ - @TableField("content") - private String content; - - /** - * 用户输入 - */ - @TableField("user_input") - private String userInput; - - /** - * AI回应 - */ - @TableField("ai_response") - private String aiResponse; - - /** - * 评分 - */ - @TableField("rating") - private Integer rating; - - /** - * 反馈 - */ - @TableField("feedback") - private String feedback; - - /** - * 完成时间 - */ - @TableField("completed_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime completedTime; -} diff --git a/backend/growth/server/src/main/resources/application-local.yml b/backend/growth/server/src/main/resources/application-local.yml deleted file mode 100644 index ef94987..0000000 --- a/backend/growth/server/src/main/resources/application-local.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 本地开发环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: localhost:8848 - namespace: - group: DEFAULT_GROUP - enabled: true - username: nacos - password: Peanut2817*# - metadata: - version: 1.0.0 - zone: local - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: localhost:8848 - namespace: - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: Peanut2817*# - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: 123456 - - # Redis配置 - data: - redis: - host: localhost - port: 6379 - password: - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: debug - com.baomidou.mybatisplus: debug - com.alibaba.nacos: info - file: - name: logs/emotion-growth-local.log diff --git a/backend/growth/server/src/main/resources/application-prod.yml b/backend/growth/server/src/main/resources/application-prod.yml deleted file mode 100644 index f3b6ba3..0000000 --- a/backend/growth/server/src/main/resources/application-prod.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 生产环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: prod - group: DEFAULT_GROUP - enabled: true - username: nacos - password: EmotionMuseum2025 - metadata: - version: 1.0.0 - zone: prod - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: 101.200.208.45:8848 - namespace: prod - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: EmotionMuseum2025 - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://101.200.208.45:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: EmotionMuseum2025*# - - # Redis配置 - data: - redis: - host: 101.200.208.45 - port: 6379 - password: EmotionMuseum2025*# - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: warn - com.baomidou.mybatisplus: warn - com.alibaba.nacos: error - file: - name: logs/emotion-growth-prod.log diff --git a/backend/growth/server/src/main/resources/application-test.yml b/backend/growth/server/src/main/resources/application-test.yml deleted file mode 100644 index a341561..0000000 --- a/backend/growth/server/src/main/resources/application-test.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 测试环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: test - group: DEFAULT_GROUP - enabled: true - username: nacos - password: EmotionMuseum2025 - metadata: - version: 1.0.0 - zone: test - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: 101.200.208.45:8848 - namespace: test - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: EmotionMuseum2025 - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://101.200.208.45:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: EmotionMuseum2025*# - - # Redis配置 - data: - redis: - host: 101.200.208.45 - port: 6379 - password: EmotionMuseum2025*# - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: info - com.baomidou.mybatisplus: info - com.alibaba.nacos: warn - file: - name: logs/emotion-growth-test.log diff --git a/backend/growth/server/src/main/resources/application.yml b/backend/growth/server/src/main/resources/application.yml deleted file mode 100644 index d4b2de2..0000000 --- a/backend/growth/server/src/main/resources/application.yml +++ /dev/null @@ -1,73 +0,0 @@ -server: - port: 19004 - -spring: - application: - name: emotion-growth - profiles: - active: dev - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: emotion-dev - group: DEFAULT_GROUP - enabled: false - config: - enabled: false - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 - username: root - password: 123456 - type: com.alibaba.druid.pool.DruidDataSource - druid: - initial-size: 5 - min-idle: 5 - max-active: 20 - max-wait: 60000 - time-between-eviction-runs-millis: 60000 - min-evictable-idle-time-millis: 300000 - validation-query: SELECT 1 FROM DUAL - test-while-idle: true - test-on-borrow: false - test-on-return: false - pool-prepared-statements: true - max-pool-prepared-statement-per-connection-size: 20 -redis: - data: - host: localhost - port: 6379 - password: - database: 0 - timeout: 10000ms - lettuce: - pool: - max-active: 8 - max-wait: -1ms - max-idle: 8 - min-idle: 0 - -# 监控配置 -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus - endpoint: - health: - show-details: always - metrics: - export: - prometheus: - enabled: true - -# 日志配置 -logging: - file: - path: /data/logs/emotion-museum/growth - level: - com.emotionmuseum: debug - com.baomidou.mybatisplus: debug - pattern: - console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%logger{50}] - %msg%n" diff --git a/backend/pom.xml b/backend/pom.xml deleted file mode 100644 index 013816b..0000000 --- a/backend/pom.xml +++ /dev/null @@ -1,308 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - backend - 1.0.0 - pom - backend - 情绪博物馆后端微服务父工程 - - - - common - gateway - auth - user - ai - websocket - record - growth - explore - reward - stats - admin - - - - - 17 - 17 - UTF-8 - - - 3.0.2 - 2022.0.0 - 2022.0.0.0 - - - 8.0.33 - 3.5.3.1 - 1.2.16 - - - 3.20.0 - - - 1.18.30 - 5.8.15 - 2.0.25 - 4.0.0 - 1.5.3.Final - - - 0.11.5 - 1.70 - - - 1.10.4 - - - 1.17.6 - - - - - - - - org.springframework.boot - spring-boot-dependencies - ${spring-boot.version} - pom - import - - - - - org.springframework.cloud - spring-cloud-dependencies - ${spring-cloud.version} - pom - import - - - - - com.alibaba.cloud - spring-cloud-alibaba-dependencies - ${spring-cloud-alibaba.version} - pom - import - - - - - mysql - mysql-connector-java - ${mysql.version} - - - - com.baomidou - mybatis-plus-boot-starter - ${mybatis-plus.version} - - - - com.alibaba - druid-spring-boot-starter - ${druid.version} - - - - - org.redisson - redisson-spring-boot-starter - ${redisson.version} - - - - - cn.hutool - hutool-all - ${hutool.version} - - - - com.alibaba.fastjson2 - fastjson2 - ${fastjson2.version} - - - - - com.github.xiaoymin - knife4j-openapi3-spring-boot-starter - ${knife4j.version} - - - - - org.mapstruct - mapstruct - ${mapstruct.version} - - - - org.mapstruct - mapstruct-processor - ${mapstruct.version} - - - - - io.jsonwebtoken - jjwt-api - ${jwt.version} - - - - io.jsonwebtoken - jjwt-impl - ${jwt.version} - - - - io.jsonwebtoken - jjwt-jackson - ${jwt.version} - - - - - org.bouncycastle - bcprov-jdk15on - ${bcprov.version} - - - - - io.micrometer - micrometer-registry-prometheus - ${micrometer.version} - - - - - org.testcontainers - testcontainers-bom - ${testcontainers.version} - pom - import - - - - - com.emotionmuseum - common - ${project.version} - - - - - - - - - org.springframework.boot - spring-boot-starter - - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - - org.projectlombok - lombok - true - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring-boot.version} - - - - org.projectlombok - lombok - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.10.1 - - 17 - 17 - - - org.projectlombok - lombok - ${lombok.version} - - - org.mapstruct - mapstruct-processor - ${mapstruct.version} - - - - - - - - - - - - aliyun-maven - Aliyun Maven Repository - https://maven.aliyun.com/repository/public - - true - - - false - - - - - - - aliyun-maven - Aliyun Maven Repository - https://maven.aliyun.com/repository/public - - true - - - false - - - - \ No newline at end of file diff --git a/backend/record/Dockerfile b/backend/record/Dockerfile deleted file mode 100644 index d258765..0000000 --- a/backend/record/Dockerfile +++ /dev/null @@ -1,48 +0,0 @@ -# 记录服务Dockerfile -FROM openjdk:17-jdk-alpine - -# 设置工作目录 -WORKDIR /app - -# 安装必要的工具 -RUN apk add --no-cache curl tzdata && \ - cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ - echo "Asia/Shanghai" > /etc/timezone - -# 复制Maven构建文件 -COPY pom.xml ./ -COPY emotion-common ./emotion-common -COPY emotion-record ./emotion-record - -# 安装Maven -RUN apk add --no-cache maven - -# 构建应用 -RUN mvn clean package -DskipTests -pl emotion-record -am - -# 创建运行用户 -RUN addgroup -g 1000 emotion && \ - adduser -D -s /bin/sh -u 1000 -G emotion emotion - -# 复制jar文件 -RUN cp emotion-record/target/emotion-record-*.jar app.jar - -# 设置文件权限 -RUN chown -R emotion:emotion /app - -# 切换到非root用户 -USER emotion - -# 健康检查 -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ - CMD curl -f http://localhost:19003/actuator/health || exit 1 - -# 暴露端口 -EXPOSE 19003 - -# 启动命令 -ENTRYPOINT ["java", "-jar", \ - "-Xms512m", "-Xmx1024m", \ - "-Djava.security.egd=file:/dev/./urandom", \ - "-Dspring.profiles.active=local", \ - "app.jar"] diff --git a/backend/record/api/pom.xml b/backend/record/api/pom.xml deleted file mode 100644 index bc44045..0000000 --- a/backend/record/api/pom.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - record - 1.0.0 - ../pom.xml - - record-api - record-api - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - com.emotionmuseum - common - ${project.version} - - - diff --git a/backend/record/api/src/main/java/com/emotionmuseum/record/api/client/RecordClient.java b/backend/record/api/src/main/java/com/emotionmuseum/record/api/client/RecordClient.java deleted file mode 100644 index b986e31..0000000 --- a/backend/record/api/src/main/java/com/emotionmuseum/record/api/client/RecordClient.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.emotionmuseum.record.api.client; - -import com.emotionmuseum.common.result.Result; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; - -@FeignClient(name = "record") -public interface RecordClient { - - @GetMapping("/record/health") - Result health(); -} - - diff --git a/backend/record/deploy.sh b/backend/record/deploy.sh deleted file mode 100644 index 30264f6..0000000 --- a/backend/record/deploy.sh +++ /dev/null @@ -1,226 +0,0 @@ -#!/bin/bash - -# emotion-record 单独部署脚本 -# 作者: emotion-museum -# 日期: 2025-07-18 - -set -e - -# 配置变量 -SERVICE_NAME="emotion-record" -SERVICE_PORT="19003" -REMOTE_HOST="'root@101.200.208.45'" -REMOTE_BUILD_DIR="/data/builds" -REMOTE_DOCKER_COMPOSE_DIR="/data/docker" -PROFILE="test" -PROJECT_NAME="emotion-museum" - -# 颜色输出 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -# 日志函数 -log_info() { - echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -# 检查远程服务器连接 -check_remote_connection() { - log_info "检查远程服务器连接..." - if ssh -o ConnectTimeout=10 'root@101.200.208.45' "echo 'Connection successful'" > /dev/null 2>&1; then - log_success "远程服务器连接正常" - else - log_error "无法连接到远程服务器 'root@101.200.208.45'" - exit 1 - fi -} - -# 构建服务 -build_service() { - log_info "构建服务: $SERVICE_NAME" - - # 构建父项目依赖 - cd .. - mvn clean install -DskipTests -q - cd emotion-record - - # 构建当前服务 - if mvn clean package -DskipTests -Ptest -q; then - log_success "服务 $SERVICE_NAME 构建成功" - else - log_error "服务 $SERVICE_NAME 构建失败" - exit 1 - fi -} - -# 创建Dockerfile -create_dockerfile() { - log_info "创建Dockerfile: $SERVICE_NAME" - - ssh 'root@101.200.208.45' "cat > $REMOTE_DOCKER_COMPOSE_DIR/Dockerfile.${SERVICE_NAME} << 'EOF' -# 使用阿里云镜像源的OpenJDK -# 使用Java 17 Alpine镜像 -FROM openjdk:17-alpine - -WORKDIR /app - -# 安装必要的工具 (Alpine Linux使用apk) -RUN apk add --no-cache curl - -COPY ${SERVICE_NAME}-1.0.0.jar app.jar - -RUN mkdir -p /app/logs - -ENV TZ=Asia/Shanghai -RUN ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone - -EXPOSE ${SERVICE_PORT} - -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \\ - CMD curl -f http://localhost:${SERVICE_PORT}/actuator/health || exit 1 - -ENTRYPOINT [\"java\", \"-Djava.security.egd=file:/dev/./urandom\", \"-Xms512m\", \"-Xmx1024m\", \"-jar\", \"app.jar\"] -EOF" -} - -# 部署服务 -deploy_service() { - log_info "开始部署服务: $SERVICE_NAME" - - # 检查jar包 - local jar_file="target/${SERVICE_NAME}-1.0.0.jar" - if [ ! -f "$jar_file" ]; then - log_error "JAR包不存在: $jar_file" - exit 1 - fi - - # 创建远程目录 - ssh 'root@101.200.208.45' " - mkdir -p $REMOTE_BUILD_DIR - mkdir -p $REMOTE_DOCKER_COMPOSE_DIR - mkdir -p /data/logs/emotion-museum - " - - # 删除旧jar包 - log_info "删除远程旧jar包" - ssh 'root@101.200.208.45' "rm -f $REMOTE_BUILD_DIR/${SERVICE_NAME}-*.jar" - - # 上传新jar包 - log_info "上传jar包" - if scp "$jar_file" 'root@101.200.208.45':$REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar; then - log_success "jar包上传成功" - else - log_error "jar包上传失败" - exit 1 - fi - - # 创建Dockerfile - create_dockerfile - - # 停止旧容器 - log_info "停止旧容器" - ssh 'root@101.200.208.45' " - docker stop ${SERVICE_NAME} 2>/dev/null || true - docker rm ${SERVICE_NAME} 2>/dev/null || true - docker rmi ${PROJECT_NAME}/${SERVICE_NAME}:latest 2>/dev/null || true - " - - # 创建Docker网络 - ssh 'root@101.200.208.45' "docker network create emotion-network 2>/dev/null || true" - - # 构建镜像 - log_info "构建Docker镜像" - ssh 'root@101.200.208.45' " - cd $REMOTE_DOCKER_COMPOSE_DIR - # 复制jar包到Docker构建目录 - cp $REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar $REMOTE_DOCKER_COMPOSE_DIR/ - - # 构建镜像 docker build -t ${PROJECT_NAME}/${SERVICE_NAME}:latest -f Dockerfile.${SERVICE_NAME} . - - # 清理临时文件 - rm -f ${SERVICE_NAME}-1.0.0.jar " - - # 启动容器 - log_info "启动新容器" - ssh 'root@101.200.208.45' " - docker run -d \\ - --name ${SERVICE_NAME} \\ - --network emotion-network \\ - -p ${SERVICE_PORT}:${SERVICE_PORT} \\ - -v /data/logs/emotion-museum:/app/logs \\ - -e SPRING_PROFILES_ACTIVE=${PROFILE} \\ - -e MYSQL_HOST=101.200.208.45 \\ - -e MYSQL_PORT=3306 \\ - -e MYSQL_DATABASE=emotion_museum \\ - -e MYSQL_USERNAME=root \\ - -e MYSQL_PASSWORD='EmotionMuseum2025*#' \\ - -e REDIS_HOST=101.200.208.45 \\ - -e REDIS_PORT=6379 \\ - -e REDIS_PASSWORD= \\ - -e REDIS_DATABASE=0 \\ - -e NACOS_SERVER_ADDR=101.200.208.45:8848 \\ - -e NACOS_USERNAME=nacos \\ - -e NACOS_PASSWORD='Peanut2817*#' \\ - --restart unless-stopped \\ - ${PROJECT_NAME}/${SERVICE_NAME}:latest - " - - # 等待启动 - log_info "等待服务启动..." - sleep 15 - - # 检查状态 - if ssh 'root@101.200.208.45' "docker ps | grep ${SERVICE_NAME}" > /dev/null; then - log_success "服务启动成功" - - # 显示日志 - log_info "服务日志 最后20行:" - ssh 'root@101.200.208.45' "docker logs --tail 20 ${SERVICE_NAME}" - - # 健康检查 - log_info "执行健康检查..." - sleep 10 - if ssh 'root@101.200.208.45' "curl -f -s http://localhost:${SERVICE_PORT}/actuator/health" > /dev/null 2>&1; then - log_success "健康检查通过" - else - log_warning "健康检查失败,服务可能仍在启动中" - fi - else - log_error "服务启动失败" - ssh 'root@101.200.208.45' "docker logs ${SERVICE_NAME}" - exit 1 - fi -} - -# 主函数 -main() { - log_info "开始部署 $SERVICE_NAME 服务" - log_info "目标服务器: $REMOTE_HOST" - log_info "服务端口: $SERVICE_PORT" - log_info "部署环境: $PROFILE" - - check_remote_connection - build_service - deploy_service - - log_success "$SERVICE_NAME 服务部署完成!" - log_info "访问地址: http://101.200.208.45:$SERVICE_PORT" -} - -# 执行主函数 -main "$@" diff --git a/backend/record/pom.xml b/backend/record/pom.xml deleted file mode 100644 index 91e1cdf..0000000 --- a/backend/record/pom.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - backend - 1.0.0 - ../pom.xml - - record - pom - - api - server - - diff --git a/backend/record/server/pom.xml b/backend/record/server/pom.xml deleted file mode 100644 index b9f9ec3..0000000 --- a/backend/record/server/pom.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - 4.0.0 - - - com.emotionmuseum - record - 1.0.0 - - - record-server - record - 情绪记录服务 - - - - com.emotionmuseum - record-api - ${project.version} - - - com.emotionmuseum - common - ${project.version} - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-validation - - - com.baomidou - mybatis-plus-boot-starter - - - mysql - mysql-connector-java - - - com.alibaba - druid-spring-boot-starter - - - org.springframework.boot - spring-boot-starter-data-redis - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-discovery - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-config - - - org.springframework.boot - spring-boot-starter-actuator - - - \ No newline at end of file diff --git a/backend/record/server/src/main/java/com/emotionmuseum/record/RecordApplication.java b/backend/record/server/src/main/java/com/emotionmuseum/record/RecordApplication.java deleted file mode 100644 index 2b6fbe1..0000000 --- a/backend/record/server/src/main/java/com/emotionmuseum/record/RecordApplication.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.emotionmuseum.record; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - - -/** - * 情绪记录服务启动类 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@SpringBootApplication -@MapperScan("com.emotionmuseum.record.mapper") -public class RecordApplication { - - public static void main(String[] args) { - SpringApplication.run(RecordApplication.class, args); - } -} diff --git a/backend/record/server/src/main/java/com/emotionmuseum/record/entity/EmotionRecord.java b/backend/record/server/src/main/java/com/emotionmuseum/record/entity/EmotionRecord.java deleted file mode 100644 index 9411712..0000000 --- a/backend/record/server/src/main/java/com/emotionmuseum/record/entity/EmotionRecord.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.emotionmuseum.record.entity; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import com.emotionmuseum.common.entity.BaseEntity; -import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.util.List; - -/** - * 情绪记录实体 - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName(value = "emotion_record", autoResultMap = true) -public class EmotionRecord extends BaseEntity { - - /** - * 用户ID - */ - @TableField("user_id") - private String userId; - - /** - * 记录日期 - */ - @TableField("record_date") - @JsonFormat(pattern = "yyyy-MM-dd") - private LocalDate recordDate; - - /** - * 情绪类型 - */ - @TableField("emotion_type") - private String emotionType; - - /** - * 情绪强度 (0.00-1.00) - */ - @TableField("intensity") - private BigDecimal intensity; - - /** - * 触发因素 - */ - @TableField("triggers") - private String triggers; - - /** - * 描述 - */ - @TableField("description") - private String description; - - /** - * 标签 - */ - @TableField(value = "tags", typeHandler = JacksonTypeHandler.class) - private List tags; - - /** - * 天气 - */ - @TableField("weather") - private String weather; - - /** - * 地点 - */ - @TableField("location") - private String location; - - /** - * 活动 - */ - @TableField("activity") - private String activity; - - /** - * 相关人物 - */ - @TableField("people") - private String people; - - /** - * 备注 - */ - @TableField("notes") - private String notes; -} diff --git a/backend/record/server/src/main/java/com/emotionmuseum/record/request/CreateEmotionRecordRequest.java b/backend/record/server/src/main/java/com/emotionmuseum/record/request/CreateEmotionRecordRequest.java deleted file mode 100644 index 785aa2c..0000000 --- a/backend/record/server/src/main/java/com/emotionmuseum/record/request/CreateEmotionRecordRequest.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.emotionmuseum.record.request; - -import com.emotionmuseum.common.request.BaseRequest; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.DecimalMax; -import jakarta.validation.constraints.DecimalMin; -import java.time.LocalDate; -import java.util.List; - -/** - * 创建情绪记录请求 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "创建情绪记录请求") -public class CreateEmotionRecordRequest extends BaseRequest { - - private static final long serialVersionUID = 1L; - - @Schema(description = "用户ID", example = "user_123") - @NotBlank(message = "用户ID不能为空") - private String userId; - - @Schema(description = "记录日期", example = "2025-07-24") - @NotNull(message = "记录日期不能为空") - private LocalDate recordDate; - - @Schema(description = "情绪类型", example = "joy") - @NotBlank(message = "情绪类型不能为空") - private String emotionType; - - @Schema(description = "情绪强度", example = "0.8") - @NotNull(message = "情绪强度不能为空") - @DecimalMin(value = "0.0", message = "情绪强度不能小于0") - @DecimalMax(value = "1.0", message = "情绪强度不能大于1") - private Double intensity; - - @Schema(description = "触发因素", example = "完成了重要项目") - private String triggers; - - @Schema(description = "详细描述", example = "今天心情很好,完成了一个重要的项目") - private String description; - - @Schema(description = "标签列表", example = "[\"工作\", \"成就感\"]") - private List tags; - - @Schema(description = "天气情况", example = "晴天") - private String weather; - - @Schema(description = "所在位置", example = "办公室") - private String location; - - @Schema(description = "当时活动", example = "工作") - private String activity; -} diff --git a/backend/record/server/src/main/java/com/emotionmuseum/record/response/EmotionRecordResponse.java b/backend/record/server/src/main/java/com/emotionmuseum/record/response/EmotionRecordResponse.java deleted file mode 100644 index 3e52c40..0000000 --- a/backend/record/server/src/main/java/com/emotionmuseum/record/response/EmotionRecordResponse.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.emotionmuseum.record.response; - -import com.emotionmuseum.common.response.BaseResponse; -import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; - -/** - * 情绪记录响应 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "情绪记录响应") -public class EmotionRecordResponse extends BaseResponse { - - private static final long serialVersionUID = 1L; - - @Schema(description = "记录ID") - private String id; - - @Schema(description = "用户ID") - private String userId; - - @Schema(description = "记录日期") - @JsonFormat(pattern = "yyyy-MM-dd") - private LocalDate recordDate; - - @Schema(description = "情绪类型") - private String emotionType; - - @Schema(description = "情绪强度") - private Double intensity; - - @Schema(description = "触发因素") - private String triggers; - - @Schema(description = "详细描述") - private String description; - - @Schema(description = "标签列表") - private List tags; - - @Schema(description = "天气情况") - private String weather; - - @Schema(description = "所在位置") - private String location; - - @Schema(description = "当时活动") - private String activity; - - @Schema(description = "创建时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; - - @Schema(description = "更新时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime updateTime; -} diff --git a/backend/record/server/src/main/resources/application-local.yml b/backend/record/server/src/main/resources/application-local.yml deleted file mode 100644 index 6d8f9ae..0000000 --- a/backend/record/server/src/main/resources/application-local.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 本地开发环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: localhost:8848 - namespace: - group: DEFAULT_GROUP - enabled: true - username: nacos - password: Peanut2817*# - metadata: - version: 1.0.0 - zone: local - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: localhost:8848 - namespace: - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: Peanut2817*# - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: 123456 - - # Redis配置 - data: - redis: - host: localhost - port: 6379 - password: - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: debug - com.baomidou.mybatisplus: debug - com.alibaba.nacos: info - file: - name: logs/emotion-record-local.log diff --git a/backend/record/server/src/main/resources/application-prod.yml b/backend/record/server/src/main/resources/application-prod.yml deleted file mode 100644 index 9b4cfbc..0000000 --- a/backend/record/server/src/main/resources/application-prod.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 生产环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: prod - group: DEFAULT_GROUP - enabled: true - username: nacos - password: EmotionMuseum2025 - metadata: - version: 1.0.0 - zone: prod - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: 101.200.208.45:8848 - namespace: prod - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: EmotionMuseum2025 - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://101.200.208.45:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: EmotionMuseum2025*# - - # Redis配置 - data: - redis: - host: 101.200.208.45 - port: 6379 - password: EmotionMuseum2025*# - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: warn - com.baomidou.mybatisplus: warn - com.alibaba.nacos: error - file: - name: logs/emotion-record-prod.log diff --git a/backend/record/server/src/main/resources/application-test.yml b/backend/record/server/src/main/resources/application-test.yml deleted file mode 100644 index 6b7a09f..0000000 --- a/backend/record/server/src/main/resources/application-test.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 测试环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: test - group: DEFAULT_GROUP - enabled: true - username: nacos - password: EmotionMuseum2025 - metadata: - version: 1.0.0 - zone: test - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: 101.200.208.45:8848 - namespace: test - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: EmotionMuseum2025 - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://101.200.208.45:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: EmotionMuseum2025*# - - # Redis配置 - data: - redis: - host: 101.200.208.45 - port: 6379 - password: EmotionMuseum2025*# - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: info - com.baomidou.mybatisplus: info - com.alibaba.nacos: warn - file: - name: logs/emotion-record-test.log diff --git a/backend/record/server/src/main/resources/application.yml b/backend/record/server/src/main/resources/application.yml deleted file mode 100644 index e798e5a..0000000 --- a/backend/record/server/src/main/resources/application.yml +++ /dev/null @@ -1,73 +0,0 @@ -server: - port: 19003 - -spring: - application: - name: emotion-record - profiles: - active: dev - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: emotion-dev - group: DEFAULT_GROUP - enabled: false - config: - enabled: false - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 - username: root - password: 123456 - type: com.alibaba.druid.pool.DruidDataSource - druid: - initial-size: 5 - min-idle: 5 - max-active: 20 - max-wait: 60000 - time-between-eviction-runs-millis: 60000 - min-evictable-idle-time-millis: 300000 - validation-query: SELECT 1 FROM DUAL - test-while-idle: true - test-on-borrow: false - test-on-return: false - pool-prepared-statements: true - max-pool-prepared-statement-per-connection-size: 20 -redis: - data: - host: localhost - port: 6379 - password: - database: 0 - timeout: 10000ms - lettuce: - pool: - max-active: 8 - max-wait: -1ms - max-idle: 8 - min-idle: 0 - -# 监控配置 -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus - endpoint: - health: - show-details: always - metrics: - export: - prometheus: - enabled: true - -# 日志配置 -logging: - file: - path: /data/logs/emotion-museum/record - level: - com.emotionmuseum: debug - com.baomidou.mybatisplus: debug - pattern: - console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%logger{50}] - %msg%n" diff --git a/backend/reward/Dockerfile b/backend/reward/Dockerfile deleted file mode 100644 index 8358482..0000000 --- a/backend/reward/Dockerfile +++ /dev/null @@ -1,48 +0,0 @@ -# 奖励服务Dockerfile -FROM openjdk:17-jdk-alpine - -# 设置工作目录 -WORKDIR /app - -# 安装必要的工具 -RUN apk add --no-cache curl tzdata && \ - cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ - echo "Asia/Shanghai" > /etc/timezone - -# 复制Maven构建文件 -COPY pom.xml ./ -COPY emotion-common ./emotion-common -COPY emotion-reward ./emotion-reward - -# 安装Maven -RUN apk add --no-cache maven - -# 构建应用 -RUN mvn clean package -DskipTests -pl emotion-reward -am - -# 创建运行用户 -RUN addgroup -g 1000 emotion && \ - adduser -D -s /bin/sh -u 1000 -G emotion emotion - -# 复制jar文件 -RUN cp emotion-reward/target/emotion-reward-*.jar app.jar - -# 设置文件权限 -RUN chown -R emotion:emotion /app - -# 切换到非root用户 -USER emotion - -# 健康检查 -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ - CMD curl -f http://localhost:19006/actuator/health || exit 1 - -# 暴露端口 -EXPOSE 19006 - -# 启动命令 -ENTRYPOINT ["java", "-jar", \ - "-Xms512m", "-Xmx1024m", \ - "-Djava.security.egd=file:/dev/./urandom", \ - "-Dspring.profiles.active=local", \ - "app.jar"] diff --git a/backend/reward/api/pom.xml b/backend/reward/api/pom.xml deleted file mode 100644 index f547ecd..0000000 --- a/backend/reward/api/pom.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - reward - 1.0.0 - ../pom.xml - - reward-api - reward-api - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - com.emotionmuseum - common - ${project.version} - - - diff --git a/backend/reward/api/src/main/java/com/emotionmuseum/reward/api/client/RewardClient.java b/backend/reward/api/src/main/java/com/emotionmuseum/reward/api/client/RewardClient.java deleted file mode 100644 index 3fe97c9..0000000 --- a/backend/reward/api/src/main/java/com/emotionmuseum/reward/api/client/RewardClient.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.emotionmuseum.reward.api.client; - -import com.emotionmuseum.common.result.Result; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; - -@FeignClient(name = "reward") -public interface RewardClient { - - @GetMapping("/reward/health") - Result health(); -} - - diff --git a/backend/reward/deploy.sh b/backend/reward/deploy.sh deleted file mode 100644 index e328702..0000000 --- a/backend/reward/deploy.sh +++ /dev/null @@ -1,226 +0,0 @@ -#!/bin/bash - -# emotion-reward 单独部署脚本 -# 作者: emotion-museum -# 日期: 2025-07-18 - -set -e - -# 配置变量 -SERVICE_NAME="emotion-reward" -SERVICE_PORT="19006" -REMOTE_HOST="'root@101.200.208.45'" -REMOTE_BUILD_DIR="/data/builds" -REMOTE_DOCKER_COMPOSE_DIR="/data/docker" -PROFILE="test" -PROJECT_NAME="emotion-museum" - -# 颜色输出 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -# 日志函数 -log_info() { - echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -# 检查远程服务器连接 -check_remote_connection() { - log_info "检查远程服务器连接..." - if ssh -o ConnectTimeout=10 'root@101.200.208.45' "echo 'Connection successful'" > /dev/null 2>&1; then - log_success "远程服务器连接正常" - else - log_error "无法连接到远程服务器 'root@101.200.208.45'" - exit 1 - fi -} - -# 构建服务 -build_service() { - log_info "构建服务: $SERVICE_NAME" - - # 构建父项目依赖 - cd .. - mvn clean install -DskipTests -q - cd emotion-reward - - # 构建当前服务 - if mvn clean package -DskipTests -Ptest -q; then - log_success "服务 $SERVICE_NAME 构建成功" - else - log_error "服务 $SERVICE_NAME 构建失败" - exit 1 - fi -} - -# 创建Dockerfile -create_dockerfile() { - log_info "创建Dockerfile: $SERVICE_NAME" - - ssh 'root@101.200.208.45' "cat > $REMOTE_DOCKER_COMPOSE_DIR/Dockerfile.${SERVICE_NAME} << 'EOF' -# 使用阿里云镜像源的OpenJDK -# 使用Java 17 Alpine镜像 -FROM openjdk:17-alpine - -WORKDIR /app - -# 安装必要的工具 (Alpine Linux使用apk) -RUN apk add --no-cache curl - -COPY ${SERVICE_NAME}-1.0.0.jar app.jar - -RUN mkdir -p /app/logs - -ENV TZ=Asia/Shanghai -RUN ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone - -EXPOSE ${SERVICE_PORT} - -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \\ - CMD curl -f http://localhost:${SERVICE_PORT}/actuator/health || exit 1 - -ENTRYPOINT [\"java\", \"-Djava.security.egd=file:/dev/./urandom\", \"-Xms512m\", \"-Xmx1024m\", \"-jar\", \"app.jar\"] -EOF" -} - -# 部署服务 -deploy_service() { - log_info "开始部署服务: $SERVICE_NAME" - - # 检查jar包 - local jar_file="target/${SERVICE_NAME}-1.0.0.jar" - if [ ! -f "$jar_file" ]; then - log_error "JAR包不存在: $jar_file" - exit 1 - fi - - # 创建远程目录 - ssh 'root@101.200.208.45' " - mkdir -p $REMOTE_BUILD_DIR - mkdir -p $REMOTE_DOCKER_COMPOSE_DIR - mkdir -p /data/logs/emotion-museum - " - - # 删除旧jar包 - log_info "删除远程旧jar包" - ssh 'root@101.200.208.45' "rm -f $REMOTE_BUILD_DIR/${SERVICE_NAME}-*.jar" - - # 上传新jar包 - log_info "上传jar包" - if scp "$jar_file" 'root@101.200.208.45':$REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar; then - log_success "jar包上传成功" - else - log_error "jar包上传失败" - exit 1 - fi - - # 创建Dockerfile - create_dockerfile - - # 停止旧容器 - log_info "停止旧容器" - ssh 'root@101.200.208.45' " - docker stop ${SERVICE_NAME} 2>/dev/null || true - docker rm ${SERVICE_NAME} 2>/dev/null || true - docker rmi ${PROJECT_NAME}/${SERVICE_NAME}:latest 2>/dev/null || true - " - - # 创建Docker网络 - ssh 'root@101.200.208.45' "docker network create emotion-network 2>/dev/null || true" - - # 构建镜像 - log_info "构建Docker镜像" - ssh 'root@101.200.208.45' " - cd $REMOTE_DOCKER_COMPOSE_DIR - # 复制jar包到Docker构建目录 - cp $REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar $REMOTE_DOCKER_COMPOSE_DIR/ - - # 构建镜像 docker build -t ${PROJECT_NAME}/${SERVICE_NAME}:latest -f Dockerfile.${SERVICE_NAME} . - - # 清理临时文件 - rm -f ${SERVICE_NAME}-1.0.0.jar " - - # 启动容器 - log_info "启动新容器" - ssh 'root@101.200.208.45' " - docker run -d \\ - --name ${SERVICE_NAME} \\ - --network emotion-network \\ - -p ${SERVICE_PORT}:${SERVICE_PORT} \\ - -v /data/logs/emotion-museum:/app/logs \\ - -e SPRING_PROFILES_ACTIVE=${PROFILE} \\ - -e MYSQL_HOST=101.200.208.45 \\ - -e MYSQL_PORT=3306 \\ - -e MYSQL_DATABASE=emotion_museum \\ - -e MYSQL_USERNAME=root \\ - -e MYSQL_PASSWORD='EmotionMuseum2025*#' \\ - -e REDIS_HOST=101.200.208.45 \\ - -e REDIS_PORT=6379 \\ - -e REDIS_PASSWORD= \\ - -e REDIS_DATABASE=0 \\ - -e NACOS_SERVER_ADDR=101.200.208.45:8848 \\ - -e NACOS_USERNAME=nacos \\ - -e NACOS_PASSWORD='Peanut2817*#' \\ - --restart unless-stopped \\ - ${PROJECT_NAME}/${SERVICE_NAME}:latest - " - - # 等待启动 - log_info "等待服务启动..." - sleep 15 - - # 检查状态 - if ssh 'root@101.200.208.45' "docker ps | grep ${SERVICE_NAME}" > /dev/null; then - log_success "服务启动成功" - - # 显示日志 - log_info "服务日志 最后20行:" - ssh 'root@101.200.208.45' "docker logs --tail 20 ${SERVICE_NAME}" - - # 健康检查 - log_info "执行健康检查..." - sleep 10 - if ssh 'root@101.200.208.45' "curl -f -s http://localhost:${SERVICE_PORT}/actuator/health" > /dev/null 2>&1; then - log_success "健康检查通过" - else - log_warning "健康检查失败,服务可能仍在启动中" - fi - else - log_error "服务启动失败" - ssh 'root@101.200.208.45' "docker logs ${SERVICE_NAME}" - exit 1 - fi -} - -# 主函数 -main() { - log_info "开始部署 $SERVICE_NAME 服务" - log_info "目标服务器: $REMOTE_HOST" - log_info "服务端口: $SERVICE_PORT" - log_info "部署环境: $PROFILE" - - check_remote_connection - build_service - deploy_service - - log_success "$SERVICE_NAME 服务部署完成!" - log_info "访问地址: http://101.200.208.45:$SERVICE_PORT" -} - -# 执行主函数 -main "$@" diff --git a/backend/reward/pom.xml b/backend/reward/pom.xml deleted file mode 100644 index 296980b..0000000 --- a/backend/reward/pom.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - backend - 1.0.0 - ../pom.xml - - reward - pom - - api - server - - diff --git a/backend/reward/server/pom.xml b/backend/reward/server/pom.xml deleted file mode 100644 index f50b934..0000000 --- a/backend/reward/server/pom.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - 4.0.0 - - - com.emotionmuseum - reward - 1.0.0 - - - reward-server - reward - 奖励系统服务 - - - - com.emotionmuseum - reward-api - ${project.version} - - - com.emotionmuseum - common - ${project.version} - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-validation - - - com.baomidou - mybatis-plus-boot-starter - - - mysql - mysql-connector-java - - - com.alibaba - druid-spring-boot-starter - - - org.springframework.boot - spring-boot-starter-data-redis - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-discovery - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-config - - - org.springframework.boot - spring-boot-starter-actuator - - - \ No newline at end of file diff --git a/backend/reward/server/src/main/java/com/emotionmuseum/reward/RewardApplication.java b/backend/reward/server/src/main/java/com/emotionmuseum/reward/RewardApplication.java deleted file mode 100644 index 760796c..0000000 --- a/backend/reward/server/src/main/java/com/emotionmuseum/reward/RewardApplication.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.emotionmuseum.reward; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - - -/** - * 成就奖励服务启动类 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@SpringBootApplication -@MapperScan("com.emotionmuseum.reward.mapper") -public class RewardApplication { - - public static void main(String[] args) { - SpringApplication.run(RewardApplication.class, args); - } -} diff --git a/backend/reward/server/src/main/java/com/emotionmuseum/reward/entity/Achievement.java b/backend/reward/server/src/main/java/com/emotionmuseum/reward/entity/Achievement.java deleted file mode 100644 index cc416e1..0000000 --- a/backend/reward/server/src/main/java/com/emotionmuseum/reward/entity/Achievement.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.emotionmuseum.reward.entity; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import com.emotionmuseum.common.entity.BaseEntity; -import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.Map; - -/** - * 成就实体 - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName(value = "achievement", autoResultMap = true) -public class Achievement extends BaseEntity { - - /** - * 成就标题 - */ - @TableField("title") - private String title; - - /** - * 描述 - */ - @TableField("description") - private String description; - - /** - * 分类 - */ - @TableField("category") - private String category; - - /** - * 图标 - */ - @TableField("icon") - private String icon; - - /** - * 稀有度 - */ - @TableField("rarity") - private String rarity; - - /** - * 条件类型 - */ - @TableField("condition_type") - private String conditionType; - - /** - * 条件值 - */ - @TableField(value = "condition_value", typeHandler = JacksonTypeHandler.class) - private Map conditionValue; - - /** - * 奖励 - */ - @TableField(value = "rewards", typeHandler = JacksonTypeHandler.class) - private Map rewards; - - /** - * 解锁时间 - */ - @TableField("unlocked_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime unlockedTime; - - /** - * 进度 (0.00-100.00) - */ - @TableField("progress") - private BigDecimal progress; - - /** - * 是否隐藏: 0-显示, 1-隐藏 - */ - @TableField("is_hidden") - private Integer isHidden; -} diff --git a/backend/reward/server/src/main/java/com/emotionmuseum/reward/entity/Reward.java b/backend/reward/server/src/main/java/com/emotionmuseum/reward/entity/Reward.java deleted file mode 100644 index 8c714a1..0000000 --- a/backend/reward/server/src/main/java/com/emotionmuseum/reward/entity/Reward.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.emotionmuseum.reward.entity; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import com.emotionmuseum.common.entity.BaseEntity; -import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.time.LocalDateTime; -import java.util.Map; - -/** - * 奖励实体 - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName(value = "reward", autoResultMap = true) -public class Reward extends BaseEntity { - - /** - * 课题ID - */ - @TableField("topic_id") - private String topicId; - - /** - * 成就ID - */ - @TableField("achievement_id") - private String achievementId; - - /** - * 奖励类型 - */ - @TableField("type") - private String type; - - /** - * 奖励名称 - */ - @TableField("name") - private String name; - - /** - * 描述 - */ - @TableField("description") - private String description; - - /** - * 图标 - */ - @TableField("icon") - private String icon; - - /** - * 稀有度 - */ - @TableField("rarity") - private String rarity; - - /** - * 奖励值 - */ - @TableField(value = "value", typeHandler = JacksonTypeHandler.class) - private Map value; - - /** - * 获得时间 - */ - @TableField("earned_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime earnedTime; - - /** - * 是否新获得: 0-已查看, 1-新获得 - */ - @TableField("is_new") - private Integer isNew; -} diff --git a/backend/reward/server/src/main/resources/application-local.yml b/backend/reward/server/src/main/resources/application-local.yml deleted file mode 100644 index 2651330..0000000 --- a/backend/reward/server/src/main/resources/application-local.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 本地开发环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: localhost:8848 - namespace: - group: DEFAULT_GROUP - enabled: true - username: nacos - password: Peanut2817*# - metadata: - version: 1.0.0 - zone: local - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: localhost:8848 - namespace: - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: Peanut2817*# - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: 123456 - - # Redis配置 - data: - redis: - host: localhost - port: 6379 - password: - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: debug - com.baomidou.mybatisplus: debug - com.alibaba.nacos: info - file: - name: logs/emotion-reward-local.log diff --git a/backend/reward/server/src/main/resources/application-prod.yml b/backend/reward/server/src/main/resources/application-prod.yml deleted file mode 100644 index 9740a2c..0000000 --- a/backend/reward/server/src/main/resources/application-prod.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 生产环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: prod - group: DEFAULT_GROUP - enabled: true - username: nacos - password: EmotionMuseum2025 - metadata: - version: 1.0.0 - zone: prod - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: 101.200.208.45:8848 - namespace: prod - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: EmotionMuseum2025 - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://101.200.208.45:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: EmotionMuseum2025*# - - # Redis配置 - data: - redis: - host: 101.200.208.45 - port: 6379 - password: EmotionMuseum2025*# - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: warn - com.baomidou.mybatisplus: warn - com.alibaba.nacos: error - file: - name: logs/emotion-reward-prod.log diff --git a/backend/reward/server/src/main/resources/application-test.yml b/backend/reward/server/src/main/resources/application-test.yml deleted file mode 100644 index b8b3c48..0000000 --- a/backend/reward/server/src/main/resources/application-test.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 测试环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: test - group: DEFAULT_GROUP - enabled: true - username: nacos - password: EmotionMuseum2025 - metadata: - version: 1.0.0 - zone: test - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: 101.200.208.45:8848 - namespace: test - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: EmotionMuseum2025 - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://101.200.208.45:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: EmotionMuseum2025*# - - # Redis配置 - data: - redis: - host: 101.200.208.45 - port: 6379 - password: EmotionMuseum2025*# - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: info - com.baomidou.mybatisplus: info - com.alibaba.nacos: warn - file: - name: logs/emotion-reward-test.log diff --git a/backend/reward/server/src/main/resources/application.yml b/backend/reward/server/src/main/resources/application.yml deleted file mode 100644 index 8ef4c64..0000000 --- a/backend/reward/server/src/main/resources/application.yml +++ /dev/null @@ -1,73 +0,0 @@ -server: - port: 19006 - -spring: - application: - name: emotion-reward - profiles: - active: dev - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: emotion-dev - group: DEFAULT_GROUP - enabled: false - config: - enabled: false - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 - username: root - password: 123456 - type: com.alibaba.druid.pool.DruidDataSource - druid: - initial-size: 5 - min-idle: 5 - max-active: 20 - max-wait: 60000 - time-between-eviction-runs-millis: 60000 - min-evictable-idle-time-millis: 300000 - validation-query: SELECT 1 FROM DUAL - test-while-idle: true - test-on-borrow: false - test-on-return: false - pool-prepared-statements: true - max-pool-prepared-statement-per-connection-size: 20 - redis: - data: - host: localhost - port: 6379 - password: - database: 0 - timeout: 10000ms - lettuce: - pool: - max-active: 8 - max-wait: -1ms - max-idle: 8 - min-idle: 0 - -# 监控配置 -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus - endpoint: - health: - show-details: always - metrics: - export: - prometheus: - enabled: true - -# 日志配置 -logging: - file: - path: /data/logs/emotion-museum/reward - level: - com.emotionmuseum: debug - com.baomidou.mybatisplus: debug - pattern: - console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%logger{50}] - %msg%n" diff --git a/backend/stats/Dockerfile b/backend/stats/Dockerfile deleted file mode 100644 index 618f7ab..0000000 --- a/backend/stats/Dockerfile +++ /dev/null @@ -1,48 +0,0 @@ -# 统计服务Dockerfile -FROM openjdk:17-jdk-alpine - -# 设置工作目录 -WORKDIR /app - -# 安装必要的工具 -RUN apk add --no-cache curl tzdata && \ - cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ - echo "Asia/Shanghai" > /etc/timezone - -# 复制Maven构建文件 -COPY pom.xml ./ -COPY emotion-common ./emotion-common -COPY emotion-stats ./emotion-stats - -# 安装Maven -RUN apk add --no-cache maven - -# 构建应用 -RUN mvn clean package -DskipTests -pl emotion-stats -am - -# 创建运行用户 -RUN addgroup -g 1000 emotion && \ - adduser -D -s /bin/sh -u 1000 -G emotion emotion - -# 复制jar文件 -RUN cp emotion-stats/target/emotion-stats-*.jar app.jar - -# 设置文件权限 -RUN chown -R emotion:emotion /app - -# 切换到非root用户 -USER emotion - -# 健康检查 -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ - CMD curl -f http://localhost:19007/actuator/health || exit 1 - -# 暴露端口 -EXPOSE 19007 - -# 启动命令 -ENTRYPOINT ["java", "-jar", \ - "-Xms512m", "-Xmx1024m", \ - "-Djava.security.egd=file:/dev/./urandom", \ - "-Dspring.profiles.active=local", \ - "app.jar"] diff --git a/backend/stats/api/pom.xml b/backend/stats/api/pom.xml deleted file mode 100644 index fb74580..0000000 --- a/backend/stats/api/pom.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - stats - 1.0.0 - ../pom.xml - - stats-api - stats-api - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - com.emotionmuseum - common - ${project.version} - - - diff --git a/backend/stats/api/src/main/java/com/emotionmuseum/stats/api/client/StatsClient.java b/backend/stats/api/src/main/java/com/emotionmuseum/stats/api/client/StatsClient.java deleted file mode 100644 index 70aaff4..0000000 --- a/backend/stats/api/src/main/java/com/emotionmuseum/stats/api/client/StatsClient.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.emotionmuseum.stats.api.client; - -import com.emotionmuseum.common.result.Result; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; - -@FeignClient(name = "stats") -public interface StatsClient { - - @GetMapping("/stats/health") - Result health(); -} - - diff --git a/backend/stats/deploy.sh b/backend/stats/deploy.sh deleted file mode 100644 index 073c027..0000000 --- a/backend/stats/deploy.sh +++ /dev/null @@ -1,226 +0,0 @@ -#!/bin/bash - -# emotion-stats 单独部署脚本 -# 作者: emotion-museum -# 日期: 2025-07-18 - -set -e - -# 配置变量 -SERVICE_NAME="emotion-stats" -SERVICE_PORT="19009" -REMOTE_HOST="'root@101.200.208.45'" -REMOTE_BUILD_DIR="/data/builds" -REMOTE_DOCKER_COMPOSE_DIR="/data/docker" -PROFILE="test" -PROJECT_NAME="emotion-museum" - -# 颜色输出 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -# 日志函数 -log_info() { - echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -# 检查远程服务器连接 -check_remote_connection() { - log_info "检查远程服务器连接..." - if ssh -o ConnectTimeout=10 'root@101.200.208.45' "echo 'Connection successful'" > /dev/null 2>&1; then - log_success "远程服务器连接正常" - else - log_error "无法连接到远程服务器 'root@101.200.208.45'" - exit 1 - fi -} - -# 构建服务 -build_service() { - log_info "构建服务: $SERVICE_NAME" - - # 构建父项目依赖 - cd .. - mvn clean install -DskipTests -q - cd emotion-stats - - # 构建当前服务 - if mvn clean package -DskipTests -Ptest -q; then - log_success "服务 $SERVICE_NAME 构建成功" - else - log_error "服务 $SERVICE_NAME 构建失败" - exit 1 - fi -} - -# 创建Dockerfile -create_dockerfile() { - log_info "创建Dockerfile: $SERVICE_NAME" - - ssh 'root@101.200.208.45' "cat > $REMOTE_DOCKER_COMPOSE_DIR/Dockerfile.${SERVICE_NAME} << 'EOF' -# 使用阿里云镜像源的OpenJDK -# 使用Java 17 Alpine镜像 -FROM openjdk:17-alpine - -WORKDIR /app - -# 安装必要的工具 (Alpine Linux使用apk) -RUN apk add --no-cache curl - -COPY ${SERVICE_NAME}-1.0.0.jar app.jar - -RUN mkdir -p /app/logs - -ENV TZ=Asia/Shanghai -RUN ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone - -EXPOSE ${SERVICE_PORT} - -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \\ - CMD curl -f http://localhost:${SERVICE_PORT}/actuator/health || exit 1 - -ENTRYPOINT [\"java\", \"-Djava.security.egd=file:/dev/./urandom\", \"-Xms512m\", \"-Xmx1024m\", \"-jar\", \"app.jar\"] -EOF" -} - -# 部署服务 -deploy_service() { - log_info "开始部署服务: $SERVICE_NAME" - - # 检查jar包 - local jar_file="target/${SERVICE_NAME}-1.0.0.jar" - if [ ! -f "$jar_file" ]; then - log_error "JAR包不存在: $jar_file" - exit 1 - fi - - # 创建远程目录 - ssh 'root@101.200.208.45' " - mkdir -p $REMOTE_BUILD_DIR - mkdir -p $REMOTE_DOCKER_COMPOSE_DIR - mkdir -p /data/logs/emotion-museum - " - - # 删除旧jar包 - log_info "删除远程旧jar包" - ssh 'root@101.200.208.45' "rm -f $REMOTE_BUILD_DIR/${SERVICE_NAME}-*.jar" - - # 上传新jar包 - log_info "上传jar包" - if scp "$jar_file" 'root@101.200.208.45':$REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar; then - log_success "jar包上传成功" - else - log_error "jar包上传失败" - exit 1 - fi - - # 创建Dockerfile - create_dockerfile - - # 停止旧容器 - log_info "停止旧容器" - ssh 'root@101.200.208.45' " - docker stop ${SERVICE_NAME} 2>/dev/null || true - docker rm ${SERVICE_NAME} 2>/dev/null || true - docker rmi ${PROJECT_NAME}/${SERVICE_NAME}:latest 2>/dev/null || true - " - - # 创建Docker网络 - ssh 'root@101.200.208.45' "docker network create emotion-network 2>/dev/null || true" - - # 构建镜像 - log_info "构建Docker镜像" - ssh 'root@101.200.208.45' " - cd $REMOTE_DOCKER_COMPOSE_DIR - # 复制jar包到Docker构建目录 - cp $REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar $REMOTE_DOCKER_COMPOSE_DIR/ - - # 构建镜像 docker build -t ${PROJECT_NAME}/${SERVICE_NAME}:latest -f Dockerfile.${SERVICE_NAME} . - - # 清理临时文件 - rm -f ${SERVICE_NAME}-1.0.0.jar " - - # 启动容器 - log_info "启动新容器" - ssh 'root@101.200.208.45' " - docker run -d \\ - --name ${SERVICE_NAME} \\ - --network emotion-network \\ - -p ${SERVICE_PORT}:${SERVICE_PORT} \\ - -v /data/logs/emotion-museum:/app/logs \\ - -e SPRING_PROFILES_ACTIVE=${PROFILE} \\ - -e MYSQL_HOST=101.200.208.45 \\ - -e MYSQL_PORT=3306 \\ - -e MYSQL_DATABASE=emotion_museum \\ - -e MYSQL_USERNAME=root \\ - -e MYSQL_PASSWORD='EmotionMuseum2025*#' \\ - -e REDIS_HOST=101.200.208.45 \\ - -e REDIS_PORT=6379 \\ - -e REDIS_PASSWORD= \\ - -e REDIS_DATABASE=0 \\ - -e NACOS_SERVER_ADDR=101.200.208.45:8848 \\ - -e NACOS_USERNAME=nacos \\ - -e NACOS_PASSWORD='Peanut2817*#' \\ - --restart unless-stopped \\ - ${PROJECT_NAME}/${SERVICE_NAME}:latest - " - - # 等待启动 - log_info "等待服务启动..." - sleep 15 - - # 检查状态 - if ssh 'root@101.200.208.45' "docker ps | grep ${SERVICE_NAME}" > /dev/null; then - log_success "服务启动成功" - - # 显示日志 - log_info "服务日志 最后20行:" - ssh 'root@101.200.208.45' "docker logs --tail 20 ${SERVICE_NAME}" - - # 健康检查 - log_info "执行健康检查..." - sleep 10 - if ssh 'root@101.200.208.45' "curl -f -s http://localhost:${SERVICE_PORT}/actuator/health" > /dev/null 2>&1; then - log_success "健康检查通过" - else - log_warning "健康检查失败,服务可能仍在启动中" - fi - else - log_error "服务启动失败" - ssh 'root@101.200.208.45' "docker logs ${SERVICE_NAME}" - exit 1 - fi -} - -# 主函数 -main() { - log_info "开始部署 $SERVICE_NAME 服务" - log_info "目标服务器: $REMOTE_HOST" - log_info "服务端口: $SERVICE_PORT" - log_info "部署环境: $PROFILE" - - check_remote_connection - build_service - deploy_service - - log_success "$SERVICE_NAME 服务部署完成!" - log_info "访问地址: http://101.200.208.45:$SERVICE_PORT" -} - -# 执行主函数 -main "$@" diff --git a/backend/stats/pom.xml b/backend/stats/pom.xml deleted file mode 100644 index 09f73e4..0000000 --- a/backend/stats/pom.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - backend - 1.0.0 - ../pom.xml - - stats - pom - - api - server - - diff --git a/backend/stats/server/pom.xml b/backend/stats/server/pom.xml deleted file mode 100644 index e22d8fc..0000000 --- a/backend/stats/server/pom.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - 4.0.0 - - - com.emotionmuseum - stats - 1.0.0 - - - stats-server - stats - 统计分析服务 - - - - com.emotionmuseum - stats-api - ${project.version} - - - com.emotionmuseum - common - ${project.version} - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-validation - - - com.baomidou - mybatis-plus-boot-starter - - - mysql - mysql-connector-java - - - com.alibaba - druid-spring-boot-starter - - - org.springframework.boot - spring-boot-starter-data-redis - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-discovery - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-config - - - org.springframework.boot - spring-boot-starter-actuator - - - \ No newline at end of file diff --git a/backend/stats/server/src/main/java/com/emotionmuseum/stats/StatsApplication.java b/backend/stats/server/src/main/java/com/emotionmuseum/stats/StatsApplication.java deleted file mode 100644 index 046cc88..0000000 --- a/backend/stats/server/src/main/java/com/emotionmuseum/stats/StatsApplication.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.emotionmuseum.stats; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - - -/** - * 统计分析服务启动类 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@SpringBootApplication -@MapperScan("com.emotionmuseum.stats.mapper") -public class StatsApplication { - - public static void main(String[] args) { - SpringApplication.run(StatsApplication.class, args); - } -} diff --git a/backend/stats/server/src/main/java/com/emotionmuseum/stats/entity/UserStats.java b/backend/stats/server/src/main/java/com/emotionmuseum/stats/entity/UserStats.java deleted file mode 100644 index a07cb99..0000000 --- a/backend/stats/server/src/main/java/com/emotionmuseum/stats/entity/UserStats.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.emotionmuseum.stats.entity; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import com.emotionmuseum.common.entity.BaseEntity; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - * 用户统计实体 - * - * @author huazhongmin - * @since 2025-07-13 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName("user_stats") -public class UserStats extends BaseEntity { - - /** - * 用户ID - */ - @TableField("user_id") - private String userId; - - /** - * 总对话数 - */ - @TableField("total_conversations") - private Integer totalConversations; - - /** - * 总消息数 - */ - @TableField("total_messages") - private Integer totalMessages; - - /** - * 总情绪记录数 - */ - @TableField("total_emotions_recorded") - private Integer totalEmotionsRecorded; - - /** - * 完成的课题数 - */ - @TableField("topics_completed") - private Integer topicsCompleted; - - /** - * 解锁的成就数 - */ - @TableField("achievements_unlocked") - private Integer achievementsUnlocked; - - /** - * 总积分 - */ - @TableField("total_points") - private Integer totalPoints; - - /** - * 连续使用天数 - */ - @TableField("consecutive_days") - private Integer consecutiveDays; - - /** - * 最大连续天数 - */ - @TableField("max_consecutive_days") - private Integer maxConsecutiveDays; - - /** - * 访问的地点数 - */ - @TableField("locations_visited") - private Integer locationsVisited; - - /** - * 创建的帖子数 - */ - @TableField("posts_created") - private Integer postsCreated; - - /** - * 评论数 - */ - @TableField("comments_made") - private Integer commentsMade; - - /** - * 收到的点赞数 - */ - @TableField("likes_received") - private Integer likesReceived; - - /** - * 社交互动数 - */ - @TableField("social_interactions") - private Integer socialInteractions; -} diff --git a/backend/stats/server/src/main/resources/application-local.yml b/backend/stats/server/src/main/resources/application-local.yml deleted file mode 100644 index 744befa..0000000 --- a/backend/stats/server/src/main/resources/application-local.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 本地开发环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: localhost:8848 - namespace: - group: DEFAULT_GROUP - enabled: true - username: nacos - password: Peanut2817*# - metadata: - version: 1.0.0 - zone: local - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: localhost:8848 - namespace: - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: Peanut2817*# - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: 123456 - - # Redis配置 - data: - redis: - host: localhost - port: 6379 - password: - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: debug - com.baomidou.mybatisplus: debug - com.alibaba.nacos: info - file: - name: logs/emotion-stats-local.log diff --git a/backend/stats/server/src/main/resources/application-prod.yml b/backend/stats/server/src/main/resources/application-prod.yml deleted file mode 100644 index 0b34064..0000000 --- a/backend/stats/server/src/main/resources/application-prod.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 生产环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: prod - group: DEFAULT_GROUP - enabled: true - username: nacos - password: EmotionMuseum2025 - metadata: - version: 1.0.0 - zone: prod - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: 101.200.208.45:8848 - namespace: prod - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: EmotionMuseum2025 - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://101.200.208.45:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: EmotionMuseum2025*# - - # Redis配置 - data: - redis: - host: 101.200.208.45 - port: 6379 - password: EmotionMuseum2025*# - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: warn - com.baomidou.mybatisplus: warn - com.alibaba.nacos: error - file: - name: logs/emotion-stats-prod.log diff --git a/backend/stats/server/src/main/resources/application-test.yml b/backend/stats/server/src/main/resources/application-test.yml deleted file mode 100644 index e0fc48e..0000000 --- a/backend/stats/server/src/main/resources/application-test.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 测试环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: test - group: DEFAULT_GROUP - enabled: true - username: nacos - password: EmotionMuseum2025 - metadata: - version: 1.0.0 - zone: test - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: 101.200.208.45:8848 - namespace: test - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: EmotionMuseum2025 - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://101.200.208.45:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: EmotionMuseum2025*# - - # Redis配置 - data: - redis: - host: 101.200.208.45 - port: 6379 - password: EmotionMuseum2025*# - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: info - com.baomidou.mybatisplus: info - com.alibaba.nacos: warn - file: - name: logs/emotion-stats-test.log diff --git a/backend/stats/server/src/main/resources/application.yml b/backend/stats/server/src/main/resources/application.yml deleted file mode 100644 index 285ebfe..0000000 --- a/backend/stats/server/src/main/resources/application.yml +++ /dev/null @@ -1,73 +0,0 @@ -server: - port: 19009 - -spring: - application: - name: emotion-stats - profiles: - active: dev - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: emotion-dev - group: DEFAULT_GROUP - enabled: false - config: - enabled: false - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 - username: root - password: 123456 - type: com.alibaba.druid.pool.DruidDataSource - druid: - initial-size: 5 - min-idle: 5 - max-active: 20 - max-wait: 60000 - time-between-eviction-runs-millis: 60000 - min-evictable-idle-time-millis: 300000 - validation-query: SELECT 1 FROM DUAL - test-while-idle: true - test-on-borrow: false - test-on-return: false - pool-prepared-statements: true - max-pool-prepared-statement-per-connection-size: 20 -redis: - data: - host: localhost - port: 6379 - password: - database: 0 - timeout: 10000ms - lettuce: - pool: - max-active: 8 - max-wait: -1ms - max-idle: 8 - min-idle: 0 - -# 监控配置 -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus - endpoint: - health: - show-details: always - metrics: - export: - prometheus: - enabled: true - -# 日志配置 -logging: - file: - path: /data/logs/emotion-museum/stats - level: - com.emotionmuseum: debug - com.baomidou.mybatisplus: debug - pattern: - console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%logger{50}] - %msg%n" diff --git a/backend/user/Dockerfile b/backend/user/Dockerfile deleted file mode 100644 index 66b0b9d..0000000 --- a/backend/user/Dockerfile +++ /dev/null @@ -1,48 +0,0 @@ -# 用户服务Dockerfile -FROM openjdk:17-jdk-alpine - -# 设置工作目录 -WORKDIR /app - -# 安装必要的工具 -RUN apk add --no-cache curl tzdata && \ - cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ - echo "Asia/Shanghai" > /etc/timezone - -# 复制Maven构建文件 -COPY pom.xml ./ -COPY emotion-common ./emotion-common -COPY emotion-user ./emotion-user - -# 安装Maven -RUN apk add --no-cache maven - -# 构建应用 -RUN mvn clean package -DskipTests -pl emotion-user -am -Dstart-class=com.emotionmuseum.user.UserApplication - -# 创建运行用户 -RUN addgroup -g 1000 emotion && \ - adduser -D -s /bin/sh -u 1000 -G emotion emotion - -# 复制jar文件 -RUN cp emotion-user/target/emotion-user-*.jar app.jar - -# 设置文件权限 -RUN chown -R emotion:emotion /app - -# 切换到非root用户 -USER emotion - -# 健康检查 -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ - CMD curl -f http://localhost:19001/actuator/health || exit 1 - -# 暴露端口 -EXPOSE 19001 - -# 启动命令 -ENTRYPOINT ["java", "-jar", \ - "-Xms512m", "-Xmx1024m", \ - "-Djava.security.egd=file:/dev/./urandom", \ - "-Dspring.profiles.active=local", \ - "app.jar"] diff --git a/backend/user/api/pom.xml b/backend/user/api/pom.xml deleted file mode 100644 index 373222f..0000000 --- a/backend/user/api/pom.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - user - 1.0.0 - ../pom.xml - - user-api - user-api - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - com.emotionmuseum - common - ${project.version} - - - diff --git a/backend/user/api/src/main/java/com/emotionmuseum/user/api/client/UserClient.java b/backend/user/api/src/main/java/com/emotionmuseum/user/api/client/UserClient.java deleted file mode 100644 index 601b5c0..0000000 --- a/backend/user/api/src/main/java/com/emotionmuseum/user/api/client/UserClient.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.emotionmuseum.user.api.client; - -import com.emotionmuseum.common.result.Result; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; - -@FeignClient(name = "user") -public interface UserClient { - - @GetMapping("/user/health") - Result health(); - - @GetMapping("/user/info/{userId}") - Result getUserInfo(@PathVariable("userId") String userId); -} - - diff --git a/backend/user/deploy.sh b/backend/user/deploy.sh deleted file mode 100644 index caaf41e..0000000 --- a/backend/user/deploy.sh +++ /dev/null @@ -1,226 +0,0 @@ -#!/bin/bash - -# emotion-user 单独部署脚本 -# 作者: emotion-museum -# 日期: 2025-07-18 - -set -e - -# 配置变量 -SERVICE_NAME="emotion-user" -SERVICE_PORT="19001" -REMOTE_HOST="'root@101.200.208.45'" -REMOTE_BUILD_DIR="/data/builds" -REMOTE_DOCKER_COMPOSE_DIR="/data/docker" -PROFILE="test" -PROJECT_NAME="emotion-museum" - -# 颜色输出 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -# 日志函数 -log_info() { - echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -# 检查远程服务器连接 -check_remote_connection() { - log_info "检查远程服务器连接..." - if ssh -o ConnectTimeout=10 'root@101.200.208.45' "echo 'Connection successful'" > /dev/null 2>&1; then - log_success "远程服务器连接正常" - else - log_error "无法连接到远程服务器 'root@101.200.208.45'" - exit 1 - fi -} - -# 构建服务 -build_service() { - log_info "构建服务: $SERVICE_NAME" - - # 构建父项目依赖 - cd .. - mvn clean install -DskipTests -q - cd emotion-user - - # 构建当前服务 - if mvn clean package -DskipTests -Ptest -q; then - log_success "服务 $SERVICE_NAME 构建成功" - else - log_error "服务 $SERVICE_NAME 构建失败" - exit 1 - fi -} - -# 创建Dockerfile -create_dockerfile() { - log_info "创建Dockerfile: $SERVICE_NAME" - - ssh 'root@101.200.208.45' "cat > $REMOTE_DOCKER_COMPOSE_DIR/Dockerfile.${SERVICE_NAME} << 'EOF' -# 使用阿里云镜像源的OpenJDK -# 使用Java 17 Alpine镜像 -FROM openjdk:17-alpine - -WORKDIR /app - -# 安装必要的工具 (Alpine Linux使用apk) -RUN apk add --no-cache curl - -COPY ${SERVICE_NAME}-1.0.0.jar app.jar - -RUN mkdir -p /app/logs - -ENV TZ=Asia/Shanghai -RUN ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone - -EXPOSE ${SERVICE_PORT} - -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \\ - CMD curl -f http://localhost:${SERVICE_PORT}/actuator/health || exit 1 - -ENTRYPOINT [\"java\", \"-Djava.security.egd=file:/dev/./urandom\", \"-Xms512m\", \"-Xmx1024m\", \"-jar\", \"app.jar\"] -EOF" -} - -# 部署服务 -deploy_service() { - log_info "开始部署服务: $SERVICE_NAME" - - # 检查jar包 - local jar_file="target/${SERVICE_NAME}-1.0.0.jar" - if [ ! -f "$jar_file" ]; then - log_error "JAR包不存在: $jar_file" - exit 1 - fi - - # 创建远程目录 - ssh 'root@101.200.208.45' " - mkdir -p $REMOTE_BUILD_DIR - mkdir -p $REMOTE_DOCKER_COMPOSE_DIR - mkdir -p /data/logs/emotion-museum - " - - # 删除旧jar包 - log_info "删除远程旧jar包" - ssh 'root@101.200.208.45' "rm -f $REMOTE_BUILD_DIR/${SERVICE_NAME}-*.jar" - - # 上传新jar包 - log_info "上传jar包" - if scp "$jar_file" 'root@101.200.208.45':$REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar; then - log_success "jar包上传成功" - else - log_error "jar包上传失败" - exit 1 - fi - - # 创建Dockerfile - create_dockerfile - - # 停止旧容器 - log_info "停止旧容器" - ssh 'root@101.200.208.45' " - docker stop ${SERVICE_NAME} 2>/dev/null || true - docker rm ${SERVICE_NAME} 2>/dev/null || true - docker rmi ${PROJECT_NAME}/${SERVICE_NAME}:latest 2>/dev/null || true - " - - # 创建Docker网络 - ssh 'root@101.200.208.45' "docker network create emotion-network 2>/dev/null || true" - - # 构建镜像 - log_info "构建Docker镜像" - ssh 'root@101.200.208.45' " - cd $REMOTE_DOCKER_COMPOSE_DIR - # 复制jar包到Docker构建目录 - cp $REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar $REMOTE_DOCKER_COMPOSE_DIR/ - - # 构建镜像 docker build -t ${PROJECT_NAME}/${SERVICE_NAME}:latest -f Dockerfile.${SERVICE_NAME} . - - # 清理临时文件 - rm -f ${SERVICE_NAME}-1.0.0.jar " - - # 启动容器 - log_info "启动新容器" - ssh 'root@101.200.208.45' " - docker run -d \\ - --name ${SERVICE_NAME} \\ - --network emotion-network \\ - -p ${SERVICE_PORT}:${SERVICE_PORT} \\ - -v /data/logs/emotion-museum:/app/logs \\ - -e SPRING_PROFILES_ACTIVE=${PROFILE} \\ - -e MYSQL_HOST=101.200.208.45 \\ - -e MYSQL_PORT=3306 \\ - -e MYSQL_DATABASE=emotion_museum \\ - -e MYSQL_USERNAME=root \\ - -e MYSQL_PASSWORD='EmotionMuseum2025*#' \\ - -e REDIS_HOST=101.200.208.45 \\ - -e REDIS_PORT=6379 \\ - -e REDIS_PASSWORD= \\ - -e REDIS_DATABASE=0 \\ - -e NACOS_SERVER_ADDR=101.200.208.45:8848 \\ - -e NACOS_USERNAME=nacos \\ - -e NACOS_PASSWORD='Peanut2817*#' \\ - --restart unless-stopped \\ - ${PROJECT_NAME}/${SERVICE_NAME}:latest - " - - # 等待启动 - log_info "等待服务启动..." - sleep 15 - - # 检查状态 - if ssh 'root@101.200.208.45' "docker ps | grep ${SERVICE_NAME}" > /dev/null; then - log_success "服务启动成功" - - # 显示日志 - log_info "服务日志 最后20行:" - ssh 'root@101.200.208.45' "docker logs --tail 20 ${SERVICE_NAME}" - - # 健康检查 - log_info "执行健康检查..." - sleep 10 - if ssh 'root@101.200.208.45' "curl -f -s http://localhost:${SERVICE_PORT}/actuator/health" > /dev/null 2>&1; then - log_success "健康检查通过" - else - log_warning "健康检查失败,服务可能仍在启动中" - fi - else - log_error "服务启动失败" - ssh 'root@101.200.208.45' "docker logs ${SERVICE_NAME}" - exit 1 - fi -} - -# 主函数 -main() { - log_info "开始部署 $SERVICE_NAME 服务" - log_info "目标服务器: $REMOTE_HOST" - log_info "服务端口: $SERVICE_PORT" - log_info "部署环境: $PROFILE" - - check_remote_connection - build_service - deploy_service - - log_success "$SERVICE_NAME 服务部署完成!" - log_info "访问地址: http://101.200.208.45:$SERVICE_PORT" -} - -# 执行主函数 -main "$@" diff --git a/backend/user/pom.xml b/backend/user/pom.xml deleted file mode 100644 index 48d682c..0000000 --- a/backend/user/pom.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - backend - 1.0.0 - ../pom.xml - - user - pom - - api - server - - \ No newline at end of file diff --git a/backend/user/server/pom.xml b/backend/user/server/pom.xml deleted file mode 100644 index 360994a..0000000 --- a/backend/user/server/pom.xml +++ /dev/null @@ -1,131 +0,0 @@ - - - 4.0.0 - - - com.emotionmuseum - user - 1.0.0 - - - user-server - user - 用户服务 - - - - com.emotionmuseum - user-api - ${project.version} - - - com.emotionmuseum - common - ${project.version} - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-validation - - - com.baomidou - mybatis-plus-boot-starter - - - mysql - mysql-connector-java - - - com.alibaba - druid-spring-boot-starter - - - org.springframework.boot - spring-boot-starter-data-redis - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-discovery - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-config - - - org.springframework.boot - spring-boot-starter-actuator - - - - - org.springframework.boot - spring-boot-starter-security - - - - - io.jsonwebtoken - jjwt-api - - - io.jsonwebtoken - jjwt-impl - - - io.jsonwebtoken - jjwt-jackson - - - - - me.zhyd.oauth - JustAuth - 1.16.5 - - - - - com.github.whvcse - easy-captcha - 1.6.2 - - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.security - spring-security-test - test - - - - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring-boot.version} - - com.emotionmuseum.user.UserApplication - - - - - repackage - - - - - - - \ No newline at end of file diff --git a/backend/user/server/src/main/java/com/emotionmuseum/user/UserApplication.java b/backend/user/server/src/main/java/com/emotionmuseum/user/UserApplication.java deleted file mode 100644 index a542e4e..0000000 --- a/backend/user/server/src/main/java/com/emotionmuseum/user/UserApplication.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.emotionmuseum.user; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; - -/** - * 用户服务启动类 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@SpringBootApplication(scanBasePackages = {"com.emotionmuseum"}) -@EnableDiscoveryClient -@MapperScan("com.emotionmuseum.user.mapper") -public class UserApplication { - - public static void main(String[] args) { - SpringApplication.run(UserApplication.class, args); - } -} diff --git a/backend/user/server/src/main/java/com/emotionmuseum/user/config/AuthenticationConfig.java b/backend/user/server/src/main/java/com/emotionmuseum/user/config/AuthenticationConfig.java deleted file mode 100644 index f4d1b29..0000000 --- a/backend/user/server/src/main/java/com/emotionmuseum/user/config/AuthenticationConfig.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.emotionmuseum.user.config; - -import com.emotionmuseum.user.security.UserDetailsServiceImpl; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; -import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; - -/** - * 认证配置类 - * 独立的认证配置,避免循环依赖 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Configuration -@RequiredArgsConstructor -public class AuthenticationConfig { - - private final UserDetailsServiceImpl userDetailsService; - - /** - * 密码编码器 - */ - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - - /** - * 认证提供者 - */ - @Bean - public AuthenticationProvider authenticationProvider() { - DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); - authProvider.setUserDetailsService(userDetailsService); - authProvider.setPasswordEncoder(passwordEncoder()); - return authProvider; - } - - /** - * 认证管理器 - */ - @Bean - public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { - return config.getAuthenticationManager(); - } -} diff --git a/backend/user/server/src/main/java/com/emotionmuseum/user/config/CaptchaConfig.java b/backend/user/server/src/main/java/com/emotionmuseum/user/config/CaptchaConfig.java deleted file mode 100644 index b47b74f..0000000 --- a/backend/user/server/src/main/java/com/emotionmuseum/user/config/CaptchaConfig.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.emotionmuseum.user.config; - -import com.wf.captcha.ArithmeticCaptcha; -import com.wf.captcha.ChineseCaptcha; -import com.wf.captcha.GifCaptcha; -import com.wf.captcha.SpecCaptcha; -import com.wf.captcha.base.Captcha; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * 验证码配置 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Configuration -public class CaptchaConfig { - - /** - * 算术验证码 - */ - @Bean("arithmeticCaptcha") - public Captcha arithmeticCaptcha() { - ArithmeticCaptcha captcha = new ArithmeticCaptcha(130, 48); - captcha.setLen(2); // 几位数运算,默认是两位 - return captcha; - } - - /** - * 中文验证码 - */ - @Bean("chineseCaptcha") - public Captcha chineseCaptcha() { - ChineseCaptcha captcha = new ChineseCaptcha(130, 48); - captcha.setLen(4); // 几个汉字,默认5个 - return captcha; - } - - /** - * GIF验证码 - */ - @Bean("gifCaptcha") - public Captcha gifCaptcha() { - GifCaptcha captcha = new GifCaptcha(130, 48); - captcha.setLen(4); // 几位数字,默认5位 - return captcha; - } - - /** - * PNG验证码 - */ - @Bean("specCaptcha") - public Captcha specCaptcha() { - SpecCaptcha captcha = new SpecCaptcha(130, 48, 4); - captcha.setCharType(Captcha.TYPE_DEFAULT); // 设置类型,纯数字、纯字母、字母数字混合 - return captcha; - } -} diff --git a/backend/user/server/src/main/java/com/emotionmuseum/user/config/OAuthConfig.java b/backend/user/server/src/main/java/com/emotionmuseum/user/config/OAuthConfig.java deleted file mode 100644 index 6a06e25..0000000 --- a/backend/user/server/src/main/java/com/emotionmuseum/user/config/OAuthConfig.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.emotionmuseum.user.config; - -import me.zhyd.oauth.config.AuthConfig; -import me.zhyd.oauth.request.*; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * 第三方登录配置 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Configuration -public class OAuthConfig { - - @Value("${oauth.wechat.client-id:}") - private String wechatClientId; - - @Value("${oauth.wechat.client-secret:}") - private String wechatClientSecret; - - @Value("${oauth.wechat.redirect-uri:}") - private String wechatRedirectUri; - - @Value("${oauth.qq.client-id:}") - private String qqClientId; - - @Value("${oauth.qq.client-secret:}") - private String qqClientSecret; - - @Value("${oauth.qq.redirect-uri:}") - private String qqRedirectUri; - - @Value("${oauth.wechat-mp.client-id:}") - private String wechatMpClientId; - - @Value("${oauth.wechat-mp.client-secret:}") - private String wechatMpClientSecret; - - @Value("${oauth.wechat-mp.redirect-uri:}") - private String wechatMpRedirectUri; - - /** - * 微信开放平台登录 - */ - @Bean - public AuthWeChatOpenRequest weChatOpenRequest() { - return new AuthWeChatOpenRequest(AuthConfig.builder() - .clientId(wechatClientId) - .clientSecret(wechatClientSecret) - .redirectUri(wechatRedirectUri) - .build()); - } - - /** - * 微信公众平台登录 - */ - @Bean - public AuthWeChatMpRequest weChatMpRequest() { - return new AuthWeChatMpRequest(AuthConfig.builder() - .clientId(wechatMpClientId) - .clientSecret(wechatMpClientSecret) - .redirectUri(wechatMpRedirectUri) - .build()); - } - - /** - * QQ登录 - */ - @Bean - public AuthQqRequest qqRequest() { - return new AuthQqRequest(AuthConfig.builder() - .clientId(qqClientId) - .clientSecret(qqClientSecret) - .redirectUri(qqRedirectUri) - .build()); - } -} diff --git a/backend/user/server/src/main/java/com/emotionmuseum/user/config/RedisConfig.java b/backend/user/server/src/main/java/com/emotionmuseum/user/config/RedisConfig.java deleted file mode 100644 index 06e495c..0000000 --- a/backend/user/server/src/main/java/com/emotionmuseum/user/config/RedisConfig.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.emotionmuseum.user.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; - -/** - * Redis配置类 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Configuration -public class RedisConfig { - - /** - * 配置RedisTemplate - */ - @Bean - public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { - RedisTemplate template = new RedisTemplate<>(); - template.setConnectionFactory(connectionFactory); - - // 使用String序列化器作为key的序列化器 - StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); - template.setKeySerializer(stringRedisSerializer); - template.setHashKeySerializer(stringRedisSerializer); - - // 使用JSON序列化器作为value的序列化器 - GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); - template.setValueSerializer(jsonRedisSerializer); - template.setHashValueSerializer(jsonRedisSerializer); - - template.afterPropertiesSet(); - return template; - } -} diff --git a/backend/user/server/src/main/java/com/emotionmuseum/user/config/SecurityConfig.java b/backend/user/server/src/main/java/com/emotionmuseum/user/config/SecurityConfig.java deleted file mode 100644 index 9fc0db8..0000000 --- a/backend/user/server/src/main/java/com/emotionmuseum/user/config/SecurityConfig.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.emotionmuseum.user.config; - -import com.emotionmuseum.user.security.JwtAuthenticationFilter; -import com.emotionmuseum.user.security.UserDetailsServiceImpl; -import lombok.RequiredArgsConstructor; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Lazy; - -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -import java.util.Arrays; -import java.util.Collections; - -/** - * Spring Security配置类 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Configuration -@EnableWebSecurity -@EnableMethodSecurity -@RequiredArgsConstructor -public class SecurityConfig { - - private final ApplicationContext applicationContext; - private final AuthenticationProvider authenticationProvider; - - - - /** - * CORS配置 - */ - @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOriginPatterns(Collections.singletonList("*")); - configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); - configuration.setAllowedHeaders(Collections.singletonList("*")); - configuration.setAllowCredentials(true); - configuration.setMaxAge(3600L); - - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", configuration); - return source; - } - - /** - * 安全过滤器链 - */ - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - // 禁用CSRF - .csrf(AbstractHttpConfigurer::disable) - - // 配置CORS - .cors(cors -> cors.configurationSource(corsConfigurationSource())) - - // 配置会话管理 - .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - - // 配置授权规则 - .authorizeHttpRequests(authz -> authz - // 公开接口 - .requestMatchers( - "/user/register", - "/user/login", - "/user/refresh", - "/user/check/**", - "/user/health", - "/captcha/**", - "/oauth/**") - .permitAll() - - // 监控和文档接口 - .requestMatchers( - "/actuator/**", - "/swagger-ui/**", - "/v3/api-docs/**", - "/doc.html", - "/swagger-resources/**", - "/webjars/**", - "/error") - .permitAll() - - // 其他接口需要认证 - .anyRequest().authenticated()) - - // 配置认证提供者 - .authenticationProvider(authenticationProvider) - - // 添加JWT过滤器 - .addFilterBefore(applicationContext.getBean(JwtAuthenticationFilter.class), - UsernamePasswordAuthenticationFilter.class); - - return http.build(); - } -} diff --git a/backend/user/server/src/main/java/com/emotionmuseum/user/controller/UserController.java b/backend/user/server/src/main/java/com/emotionmuseum/user/controller/UserController.java deleted file mode 100644 index a92b6ca..0000000 --- a/backend/user/server/src/main/java/com/emotionmuseum/user/controller/UserController.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.emotionmuseum.user.controller; - -import com.emotionmuseum.common.result.Result; -import com.emotionmuseum.user.request.UserUpdateRequest; -import com.emotionmuseum.user.service.UserService; -import com.emotionmuseum.user.response.UserInfoResponse; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.validation.Valid; - -/** - * 用户控制器 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Slf4j -@RestController -@RequestMapping("/user") -@RequiredArgsConstructor -@Validated -@Tag(name = "用户管理", description = "用户信息管理") -public class UserController { - - private final UserService userService; - - @Operation(summary = "获取用户信息") - @GetMapping("/info/{userId}") - public Result getUserInfo( - @Parameter(description = "用户ID") @PathVariable String userId) { - log.info("获取用户信息: {}", userId); - UserInfoResponse response = userService.getUserInfo(userId); - return Result.success(response); - } - - @Operation(summary = "更新用户信息") - @PutMapping("/info/{userId}") - public Result updateUserInfo( - @Parameter(description = "用户ID") @PathVariable String userId, - @Valid @RequestBody UserUpdateRequest request) { - log.info("更新用户信息: {}", userId); - UserInfoResponse response = userService.updateUserInfo(userId, request); - return Result.success("更新成功", response); - } - - @Operation(summary = "更新最后活跃时间") - @PostMapping("/active/{userId}") - public Result updateLastActiveTime( - @Parameter(description = "用户ID") @PathVariable String userId) { - userService.updateLastActiveTime(userId); - return Result.success(); - } - - @Operation(summary = "健康检查") - @GetMapping("/health") - public Result healthCheck() { - log.info("用户服务健康检查"); - return Result.success(true); - } -} diff --git a/backend/user/server/src/main/java/com/emotionmuseum/user/dto/UserUpdateRequest.java b/backend/user/server/src/main/java/com/emotionmuseum/user/dto/UserUpdateRequest.java deleted file mode 100644 index 4b629d9..0000000 --- a/backend/user/server/src/main/java/com/emotionmuseum/user/dto/UserUpdateRequest.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.emotionmuseum.user.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.Pattern; -import jakarta.validation.constraints.Size; -import java.time.LocalDate; - -/** - * 用户信息更新请求 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Data -@Schema(description = "用户信息更新请求") -public class UserUpdateRequest { - - @Schema(description = "用户名", example = "新用户名") - @Size(min = 2, max = 20, message = "用户名长度必须在2-20位之间") - private String username; - - @Schema(description = "邮箱", example = "new@example.com") - @Email(message = "邮箱格式不正确") - private String email; - - @Schema(description = "手机号", example = "13900139000") - @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") - private String phone; - - @Schema(description = "头像URL", example = "https://example.com/avatar.jpg") - private String avatar; - - @Schema(description = "昵称", example = "新昵称") - @Size(min = 1, max = 20, message = "昵称长度必须在1-20位之间") - private String nickname; - - @Schema(description = "生日", example = "1990-01-01") - private LocalDate birthDate; - - @Schema(description = "所在地", example = "上海市") - @Size(max = 50, message = "所在地长度不能超过50位") - private String location; - - @Schema(description = "个人简介", example = "更新后的个人简介") - @Size(max = 200, message = "个人简介长度不能超过200位") - private String bio; -} diff --git a/backend/user/server/src/main/java/com/emotionmuseum/user/entity/User.java b/backend/user/server/src/main/java/com/emotionmuseum/user/entity/User.java deleted file mode 100644 index a2d4f3e..0000000 --- a/backend/user/server/src/main/java/com/emotionmuseum/user/entity/User.java +++ /dev/null @@ -1,160 +0,0 @@ -package com.emotionmuseum.user.entity; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import com.emotionmuseum.common.entity.BaseEntity; -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; - -/** - * 用户实体 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName("user") -public class User extends BaseEntity { - - /** - * 账号 - */ - @TableField("account") - private String account; - - /** - * 密码 - */ - @TableField("password") - @JsonIgnore - private String password; - - /** - * 用户名 - */ - @TableField("username") - private String username; - - /** - * 邮箱 - */ - @TableField("email") - private String email; - - /** - * 手机号 - */ - @TableField("phone") - private String phone; - - /** - * 头像URL - */ - @TableField("avatar") - private String avatar; - - /** - * 昵称 - */ - @TableField("nickname") - private String nickname; - - /** - * 生日 - */ - @TableField("birth_date") - @JsonFormat(pattern = "yyyy-MM-dd") - private LocalDate birthDate; - - /** - * 所在地 - */ - @TableField("location") - private String location; - - /** - * 个人简介 - */ - @TableField("bio") - private String bio; - - /** - * 会员等级 - */ - @TableField("member_level") - private String memberLevel; - - /** - * 使用天数 - */ - @TableField("total_days") - private Integer totalDays; - - /** - * 自我感知 - */ - @TableField("self_awareness") - private BigDecimal selfAwareness; - - /** - * 情绪韧性 - */ - @TableField("emotional_resilience") - private BigDecimal emotionalResilience; - - /** - * 行动力 - */ - @TableField("action_power") - private BigDecimal actionPower; - - /** - * 共情力 - */ - @TableField("empathy") - private BigDecimal empathy; - - /** - * 生活热度 - */ - @TableField("life_enthusiasm") - private BigDecimal lifeEnthusiasm; - - /** - * 状态:0-禁用,1-正常 - */ - @TableField("status") - private Integer status; - - /** - * 是否已验证:0-未验证,1-已验证 - */ - @TableField("is_verified") - private Integer isVerified; - - /** - * 最后活跃时间 - */ - @TableField("last_active_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime lastActiveTime; - - /** - * 第三方平台ID - */ - @TableField("third_party_id") - private String thirdPartyId; - - /** - * 第三方平台类型 - */ - @TableField("third_party_type") - private String thirdPartyType; -} diff --git a/backend/user/server/src/main/java/com/emotionmuseum/user/mapper/UserMapper.java b/backend/user/server/src/main/java/com/emotionmuseum/user/mapper/UserMapper.java deleted file mode 100644 index 169765c..0000000 --- a/backend/user/server/src/main/java/com/emotionmuseum/user/mapper/UserMapper.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.emotionmuseum.user.mapper; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.emotionmuseum.user.entity.User; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; - -/** - * 用户Mapper - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Mapper -public interface UserMapper extends BaseMapper { - - /** - * 更新最后活跃时间 - * - * @param userId 用户ID - * @return 更新行数 - */ - int updateLastActiveTime(@Param("userId") String userId); -} diff --git a/backend/user/server/src/main/java/com/emotionmuseum/user/request/UserUpdateRequest.java b/backend/user/server/src/main/java/com/emotionmuseum/user/request/UserUpdateRequest.java deleted file mode 100644 index 5b3fad3..0000000 --- a/backend/user/server/src/main/java/com/emotionmuseum/user/request/UserUpdateRequest.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.emotionmuseum.user.request; - -import com.emotionmuseum.common.request.BaseRequest; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.Pattern; -import jakarta.validation.constraints.Size; -import java.time.LocalDate; - -/** - * 用户信息更新请求 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "用户信息更新请求") -public class UserUpdateRequest extends BaseRequest { - - private static final long serialVersionUID = 1L; - - @Schema(description = "用户名", example = "新用户名") - @Size(min = 2, max = 20, message = "用户名长度必须在2-20位之间") - private String username; - - @Schema(description = "邮箱", example = "new@example.com") - @Email(message = "邮箱格式不正确") - private String email; - - @Schema(description = "手机号", example = "13900139000") - @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") - private String phone; - - @Schema(description = "头像URL", example = "https://example.com/avatar.jpg") - private String avatar; - - @Schema(description = "昵称", example = "新昵称") - @Size(min = 1, max = 20, message = "昵称长度必须在1-20位之间") - private String nickname; - - @Schema(description = "生日", example = "1990-01-01") - private LocalDate birthDate; - - @Schema(description = "所在地", example = "上海市") - @Size(max = 50, message = "所在地长度不能超过50位") - private String location; - - @Schema(description = "个人简介", example = "更新后的个人简介") - @Size(max = 200, message = "个人简介长度不能超过200位") - private String bio; -} diff --git a/backend/user/server/src/main/java/com/emotionmuseum/user/response/UserInfoResponse.java b/backend/user/server/src/main/java/com/emotionmuseum/user/response/UserInfoResponse.java deleted file mode 100644 index e75f940..0000000 --- a/backend/user/server/src/main/java/com/emotionmuseum/user/response/UserInfoResponse.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.emotionmuseum.user.response; - -import com.emotionmuseum.common.response.BaseResponse; -import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; - -/** - * 用户信息响应 - * - * @author huazhongmin - * @since 2025-07-24 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "用户信息响应") -public class UserInfoResponse extends BaseResponse { - - private static final long serialVersionUID = 1L; - - @Schema(description = "用户ID") - private String id; - - @Schema(description = "账号") - private String account; - - @Schema(description = "用户名") - private String username; - - @Schema(description = "邮箱") - private String email; - - @Schema(description = "手机号") - private String phone; - - @Schema(description = "头像URL") - private String avatar; - - @Schema(description = "昵称") - private String nickname; - - @Schema(description = "生日") - @JsonFormat(pattern = "yyyy-MM-dd") - private LocalDate birthDate; - - @Schema(description = "所在地") - private String location; - - @Schema(description = "个人简介") - private String bio; - - @Schema(description = "会员等级") - private String memberLevel; - - @Schema(description = "使用天数") - private Integer totalDays; - - @Schema(description = "成长数据") - private GrowthStatsVO growthStats; - - @Schema(description = "状态") - private Integer status; - - @Schema(description = "是否已验证") - private Integer isVerified; - - @Schema(description = "创建时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; - - @Schema(description = "最后活跃时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime lastActiveTime; - - /** - * 成长数据VO - */ - @Data - @Schema(description = "成长数据") - public static class GrowthStatsVO { - - @Schema(description = "自我感知") - private BigDecimal selfAwareness; - - @Schema(description = "情绪韧性") - private BigDecimal emotionalResilience; - - @Schema(description = "行动力") - private BigDecimal actionPower; - - @Schema(description = "共情力") - private BigDecimal empathy; - - @Schema(description = "生活热度") - private BigDecimal lifeEnthusiasm; - } -} diff --git a/backend/user/server/src/main/java/com/emotionmuseum/user/security/JwtAuthenticationFilter.java b/backend/user/server/src/main/java/com/emotionmuseum/user/security/JwtAuthenticationFilter.java deleted file mode 100644 index d87e30c..0000000 --- a/backend/user/server/src/main/java/com/emotionmuseum/user/security/JwtAuthenticationFilter.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.emotionmuseum.user.security; - -import cn.hutool.core.util.StrUtil; -import com.emotionmuseum.common.util.JwtUtil; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Lazy; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -/** - * JWT认证过滤器 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Slf4j -@Component -@RequiredArgsConstructor -public class JwtAuthenticationFilter extends OncePerRequestFilter { - - private final JwtUtil jwtUtil; - @Lazy - private final UserDetailsService userDetailsService; - private final RedisTemplate redisTemplate; - - private static final String TOKEN_PREFIX = "Bearer "; - private static final String HEADER_NAME = "Authorization"; - private static final String REDIS_TOKEN_KEY_PREFIX = "auth:token:"; - - @Override - protected void doFilterInternal(HttpServletRequest request, - HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { - - try { - String token = extractTokenFromRequest(request); - - if (StrUtil.isNotBlank(token) && SecurityContextHolder.getContext().getAuthentication() == null) { - // 验证token有效性 - if (jwtUtil.validateToken(token)) { - String userId = jwtUtil.getUserIdFromToken(token); - - // 检查Redis中是否存在该token(用于登出功能) - String redisKey = REDIS_TOKEN_KEY_PREFIX + userId; - String redisToken = (String) redisTemplate.opsForValue().get(redisKey); - - if (StrUtil.isNotBlank(redisToken) && redisToken.equals(token)) { - // 加载用户详情 - UserDetails userDetails = userDetailsService.loadUserByUsername(userId); - - // 创建认证对象 - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( - userDetails, null, userDetails.getAuthorities()); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - - // 设置到安全上下文 - SecurityContextHolder.getContext().setAuthentication(authentication); - - // 更新token在Redis中的过期时间 - redisTemplate.expire(redisKey, 24, TimeUnit.HOURS); - - log.debug("JWT认证成功,用户ID: {}", userId); - } else { - log.debug("Redis中未找到有效token,用户ID: {}", userId); - } - } else { - log.debug("JWT token无效"); - } - } - } catch (Exception e) { - log.error("JWT认证过程中发生错误: {}", e.getMessage()); - } - - filterChain.doFilter(request, response); - } - - /** - * 从请求中提取token - */ - private String extractTokenFromRequest(HttpServletRequest request) { - String bearerToken = request.getHeader(HEADER_NAME); - if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) { - return bearerToken.substring(TOKEN_PREFIX.length()); - } - return null; - } - - /** - * 判断是否跳过JWT认证 - */ - @Override - protected boolean shouldNotFilter(HttpServletRequest request) { - String path = request.getRequestURI(); - - // 跳过认证的路径 - return path.startsWith("/user/register") || - path.startsWith("/user/login") || - path.startsWith("/user/refresh") || - path.startsWith("/user/check/") || - path.startsWith("/captcha/") || - path.startsWith("/oauth/") || - path.startsWith("/actuator/") || - path.startsWith("/swagger-ui/") || - path.startsWith("/v3/api-docs") || - path.startsWith("/doc.html") || - path.equals("/error"); - } -} diff --git a/backend/user/server/src/main/java/com/emotionmuseum/user/security/UserDetailsServiceImpl.java b/backend/user/server/src/main/java/com/emotionmuseum/user/security/UserDetailsServiceImpl.java deleted file mode 100644 index 16df19e..0000000 --- a/backend/user/server/src/main/java/com/emotionmuseum/user/security/UserDetailsServiceImpl.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.emotionmuseum.user.security; - -import com.emotionmuseum.user.entity.User; -import com.emotionmuseum.user.service.UserService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Lazy; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; - -import java.util.Collection; -import java.util.Collections; - -/** - * Spring Security用户详情服务实现 - * - * @author huazhongmin - * @since 2025-07-15 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class UserDetailsServiceImpl implements UserDetailsService { - - @Lazy - private final UserService userService; - - @Override - public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException { - log.debug("加载用户详情,用户ID: {}", userId); - - User user = userService.getById(userId); - if (user == null) { - log.warn("用户不存在,用户ID: {}", userId); - throw new UsernameNotFoundException("用户不存在: " + userId); - } - - if (user.getStatus() == 0) { - log.warn("用户已被禁用,用户ID: {}", userId); - throw new UsernameNotFoundException("用户已被禁用: " + userId); - } - - return new SecurityUser(user); - } - - /** - * Spring Security用户详情实现类 - */ - public static class SecurityUser implements UserDetails { - - private final User user; - - public SecurityUser(User user) { - this.user = user; - } - - @Override - public Collection getAuthorities() { - // 这里可以根据用户角色返回权限 - // 目前简单返回一个默认角色 - return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); - } - - @Override - public String getPassword() { - return user.getPassword(); - } - - @Override - public String getUsername() { - return user.getId(); - } - - /** - * 获取用户账号 - */ - public String getAccount() { - return user.getAccount(); - } - - /** - * 获取用户昵称 - */ - public String getNickname() { - return user.getNickname(); - } - - /** - * 获取用户实体 - */ - public User getUser() { - return user; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return user.getStatus() == 1; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return user.getStatus() == 1; - } - } -} diff --git a/backend/user/server/src/main/java/com/emotionmuseum/user/service/UserService.java b/backend/user/server/src/main/java/com/emotionmuseum/user/service/UserService.java deleted file mode 100644 index f3ad47c..0000000 --- a/backend/user/server/src/main/java/com/emotionmuseum/user/service/UserService.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.emotionmuseum.user.service; - -import com.baomidou.mybatisplus.extension.service.IService; -import com.emotionmuseum.user.request.UserUpdateRequest; -import com.emotionmuseum.user.entity.User; -import com.emotionmuseum.user.response.UserInfoResponse; - -/** - * 用户服务接口 - * - * @author huazhongmin - * @since 2025-07-12 - */ -public interface UserService extends IService { - - /** - * 根据用户ID获取用户信息 - * - * @param userId 用户ID - * @return 用户信息 - */ - UserInfoResponse getUserInfo(String userId); - - /** - * 更新用户信息 - * - * @param userId 用户ID - * @param request 更新请求 - * @return 用户信息 - */ - UserInfoResponse updateUserInfo(String userId, UserUpdateRequest request); - - /** - * 更新最后活跃时间 - * - * @param userId 用户ID - */ - void updateLastActiveTime(String userId); -} diff --git a/backend/user/server/src/main/java/com/emotionmuseum/user/service/impl/UserServiceImpl.java b/backend/user/server/src/main/java/com/emotionmuseum/user/service/impl/UserServiceImpl.java deleted file mode 100644 index 7d70f7b..0000000 --- a/backend/user/server/src/main/java/com/emotionmuseum/user/service/impl/UserServiceImpl.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.emotionmuseum.user.service.impl; - -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.emotionmuseum.user.request.UserUpdateRequest; -import com.emotionmuseum.user.entity.User; -import com.emotionmuseum.user.mapper.UserMapper; -import com.emotionmuseum.user.service.UserService; -import com.emotionmuseum.user.response.UserInfoResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.BeanUtils; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -/** - * 用户服务实现类 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class UserServiceImpl extends ServiceImpl implements UserService { - - @Override - public UserInfoResponse getUserInfo(String userId) { - User user = getById(userId); - if (user == null) { - throw new RuntimeException("用户不存在"); - } - return convertToUserInfoResponse(user); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public UserInfoResponse updateUserInfo(String userId, UserUpdateRequest request) { - User user = getById(userId); - if (user == null) { - throw new RuntimeException("用户不存在"); - } - - // 更新用户信息 - BeanUtils.copyProperties(request, user, "id", "account", "password"); - updateById(user); - - log.info("用户信息更新成功: {}", userId); - return convertToUserInfoResponse(user); - } - - @Override - public void updateLastActiveTime(String userId) { - baseMapper.updateLastActiveTime(userId); - } - - /** - * 转换为用户信息响应 - */ - private UserInfoResponse convertToUserInfoResponse(User user) { - UserInfoResponse response = new UserInfoResponse(); - BeanUtils.copyProperties(user, response); - - // 设置成长数据 - UserInfoResponse.GrowthStatsVO growthStats = new UserInfoResponse.GrowthStatsVO(); - growthStats.setSelfAwareness(user.getSelfAwareness()); - growthStats.setEmotionalResilience(user.getEmotionalResilience()); - growthStats.setActionPower(user.getActionPower()); - growthStats.setEmpathy(user.getEmpathy()); - growthStats.setLifeEnthusiasm(user.getLifeEnthusiasm()); - response.setGrowthStats(growthStats); - - return response; - } -} diff --git a/backend/user/server/src/main/java/com/emotionmuseum/user/vo/UserInfoResponse.java b/backend/user/server/src/main/java/com/emotionmuseum/user/vo/UserInfoResponse.java deleted file mode 100644 index 5c5e26c..0000000 --- a/backend/user/server/src/main/java/com/emotionmuseum/user/vo/UserInfoResponse.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.emotionmuseum.user.vo; - -import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; - -/** - * 用户信息响应 - * - * @author huazhongmin - * @since 2025-07-12 - */ -@Data -@Schema(description = "用户信息响应") -public class UserInfoResponse { - - @Schema(description = "用户ID") - private String id; - - @Schema(description = "账号") - private String account; - - @Schema(description = "用户名") - private String username; - - @Schema(description = "邮箱") - private String email; - - @Schema(description = "手机号") - private String phone; - - @Schema(description = "头像URL") - private String avatar; - - @Schema(description = "昵称") - private String nickname; - - @Schema(description = "生日") - @JsonFormat(pattern = "yyyy-MM-dd") - private LocalDate birthDate; - - @Schema(description = "所在地") - private String location; - - @Schema(description = "个人简介") - private String bio; - - @Schema(description = "会员等级") - private String memberLevel; - - @Schema(description = "使用天数") - private Integer totalDays; - - @Schema(description = "成长数据") - private GrowthStatsVO growthStats; - - @Schema(description = "状态") - private Integer status; - - @Schema(description = "是否已验证") - private Integer isVerified; - - @Schema(description = "创建时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; - - @Schema(description = "最后活跃时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime lastActiveTime; - - /** - * 成长数据VO - */ - @Data - @Schema(description = "成长数据") - public static class GrowthStatsVO { - - @Schema(description = "自我感知") - private BigDecimal selfAwareness; - - @Schema(description = "情绪韧性") - private BigDecimal emotionalResilience; - - @Schema(description = "行动力") - private BigDecimal actionPower; - - @Schema(description = "共情力") - private BigDecimal empathy; - - @Schema(description = "生活热度") - private BigDecimal lifeEnthusiasm; - } -} diff --git a/backend/user/server/src/main/resources/application-docker.yml b/backend/user/server/src/main/resources/application-docker.yml deleted file mode 100644 index 640b552..0000000 --- a/backend/user/server/src/main/resources/application-docker.yml +++ /dev/null @@ -1,80 +0,0 @@ -# 用户服务 Docker环境配置 -server: - port: 9001 - -spring: - application: - name: emotion-user - profiles: - active: docker - cloud: - nacos: - discovery: - server-addr: ${NACOS_SERVER_ADDR:nacos:8848} - namespace: public - group: DEFAULT_GROUP - config: - server-addr: ${NACOS_SERVER_ADDR:nacos:8848} - file-extension: yml - namespace: public - group: DEFAULT_GROUP - datasource: - url: jdbc:mysql://${MYSQL_HOST:mysql}:${MYSQL_PORT:3306}/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: 123456 - driver-class-name: com.mysql.cj.jdbc.Driver - hikari: - pool-name: EmotionUserHikariCP - minimum-idle: 5 - maximum-pool-size: 20 - auto-commit: true - idle-timeout: 30000 - max-lifetime: 1800000 - connection-timeout: 30000 - data: - redis: - host: ${REDIS_HOST:redis} - port: ${REDIS_PORT:6379} - password: - database: 2 - timeout: 6000ms - lettuce: - pool: - max-active: 8 - max-wait: -1ms - max-idle: 8 - min-idle: 0 - -# MyBatis Plus配置 -mybatis-plus: - configuration: - map-underscore-to-camel-case: true - cache-enabled: false - call-setters-on-nulls: true - jdbc-type-for-null: 'null' - log-impl: org.apache.ibatis.logging.stdout.StdOutImpl - global-config: - db-config: - id-type: assign_uuid - logic-delete-field: isDeleted - logic-delete-value: 1 - logic-not-delete-value: 0 - banner: false - -# 日志配置 -logging: - level: - com.emotionmuseum: DEBUG - com.emotionmuseum.user.mapper: DEBUG - pattern: - console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%logger{50}] - %msg%n" - -# 管理端点 -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus - endpoint: - health: - show-details: always diff --git a/backend/user/server/src/main/resources/application-local.yml b/backend/user/server/src/main/resources/application-local.yml deleted file mode 100644 index 1352248..0000000 --- a/backend/user/server/src/main/resources/application-local.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 本地开发环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: localhost:8848 - namespace: - group: DEFAULT_GROUP - enabled: true - username: nacos - password: Peanut2817*# - metadata: - version: 1.0.0 - zone: local - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: localhost:8848 - namespace: - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: Peanut2817*# - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: 123456 - - # Redis配置 - data: - redis: - host: localhost - port: 6379 - password: - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: debug - com.baomidou.mybatisplus: debug - com.alibaba.nacos: info - file: - name: logs/emotion-user-local.log diff --git a/backend/user/server/src/main/resources/application-prod.yml b/backend/user/server/src/main/resources/application-prod.yml deleted file mode 100644 index 23ced9d..0000000 --- a/backend/user/server/src/main/resources/application-prod.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 生产环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: prod - group: DEFAULT_GROUP - enabled: true - username: nacos - password: EmotionMuseum2025 - metadata: - version: 1.0.0 - zone: prod - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: 101.200.208.45:8848 - namespace: prod - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: EmotionMuseum2025 - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://101.200.208.45:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: EmotionMuseum2025*# - - # Redis配置 - data: - redis: - host: 101.200.208.45 - port: 6379 - password: EmotionMuseum2025*# - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: warn - com.baomidou.mybatisplus: warn - com.alibaba.nacos: error - file: - name: logs/emotion-user-prod.log diff --git a/backend/user/server/src/main/resources/application-test.yml b/backend/user/server/src/main/resources/application-test.yml deleted file mode 100644 index 9663627..0000000 --- a/backend/user/server/src/main/resources/application-test.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 测试环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: test - group: DEFAULT_GROUP - enabled: true - username: nacos - password: EmotionMuseum2025 - metadata: - version: 1.0.0 - zone: test - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: 101.200.208.45:8848 - namespace: test - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: EmotionMuseum2025 - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://101.200.208.45:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: EmotionMuseum2025*# - - # Redis配置 - data: - redis: - host: 101.200.208.45 - port: 6379 - password: EmotionMuseum2025*# - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: info - com.baomidou.mybatisplus: info - com.alibaba.nacos: warn - file: - name: logs/emotion-user-test.log diff --git a/backend/user/server/src/main/resources/application.yml b/backend/user/server/src/main/resources/application.yml deleted file mode 100644 index 822cddd..0000000 --- a/backend/user/server/src/main/resources/application.yml +++ /dev/null @@ -1,130 +0,0 @@ -server: - port: 19001 - -spring: - application: - name: emotion-user - - # 配置文件激活 - profiles: - active: ${SPRING_PROFILES_ACTIVE:local} - - # 允许Bean覆盖和循环引用 - main: - allow-bean-definition-overriding: true - allow-circular-references: true - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:emotion_museum}?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: ${MYSQL_USERNAME:root} - password: ${MYSQL_PASSWORD:123456} - hikari: - minimum-idle: 5 - maximum-pool-size: 20 - idle-timeout: 30000 - max-lifetime: 1800000 - connection-timeout: 30000 - connection-test-query: SELECT 1 - - # Redis配置 - data: - redis: - host: ${REDIS_HOST:localhost} - port: ${REDIS_PORT:6379} - password: ${REDIS_PASSWORD:} - database: 0 - timeout: 10000ms - lettuce: - pool: - max-active: 8 - max-wait: -1ms - max-idle: 8 - min-idle: 0 - - # Nacos配置 - cloud: - nacos: - discovery: - server-addr: ${NACOS_HOST:localhost}:${NACOS_PORT:8848} - namespace: ${NACOS_NAMESPACE:} - group: ${NACOS_GROUP:DEFAULT_GROUP} - enabled: ${NACOS_DISCOVERY_ENABLED:true} - username: ${NACOS_USERNAME:nacos} - password: ${NACOS_PASSWORD:nacos} - metadata: - version: 1.0.0 - zone: ${NACOS_ZONE:default} - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: ${NACOS_HOST:localhost}:${NACOS_PORT:8848} - namespace: ${NACOS_NAMESPACE:} - group: ${NACOS_GROUP:DEFAULT_GROUP} - file-extension: yml - enabled: ${NACOS_CONFIG_ENABLED:false} - username: ${NACOS_USERNAME:nacos} - password: ${NACOS_PASSWORD:nacos} - -mybatis-plus: - configuration: - map-underscore-to-camel-case: true - log-impl: org.apache.ibatis.logging.stdout.StdOutImpl - global-config: - db-config: - id-type: assign_uuid - logic-delete-field: deleted - logic-delete-value: 1 - logic-not-delete-value: 0 - -# 监控配置 -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus - endpoint: - health: - show-details: always - metrics: - export: - prometheus: - enabled: true - -# 日志配置 -logging: - file: - path: /data/logs/emotion-museum/user - level: - com.emotionmuseum: debug - com.baomidou.mybatisplus: debug - pattern: - console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%logger{50}] - %msg%n" - -# JWT配置 -jwt: - secret: emotion-museum-secret-key-2025 - expiration: 86400 - refresh-expiration: 604800 - -# 第三方登录配置 -oauth: - wechat: - client-id: ${WECHAT_CLIENT_ID:your_wechat_client_id} - client-secret: ${WECHAT_CLIENT_SECRET:your_wechat_client_secret} - redirect-uri: ${WECHAT_REDIRECT_URI:http://localhost:9001/oauth/callback/wechat} - wechat-mp: - client-id: ${WECHAT_MP_CLIENT_ID:your_wechat_mp_client_id} - client-secret: ${WECHAT_MP_CLIENT_SECRET:your_wechat_mp_client_secret} - redirect-uri: ${WECHAT_MP_REDIRECT_URI:http://localhost:9001/oauth/callback/wechat-mp} - qq: - client-id: ${QQ_CLIENT_ID:your_qq_client_id} - client-secret: ${QQ_CLIENT_SECRET:your_qq_client_secret} - redirect-uri: ${QQ_REDIRECT_URI:http://localhost:9001/oauth/callback/qq} diff --git a/backend/user/server/src/main/resources/mapper/UserMapper.xml b/backend/user/server/src/main/resources/mapper/UserMapper.xml deleted file mode 100644 index cf6cf37..0000000 --- a/backend/user/server/src/main/resources/mapper/UserMapper.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - UPDATE user SET last_active_time = NOW(), update_time = NOW() - WHERE id = #{userId} AND is_deleted = 0 - - \ No newline at end of file diff --git a/backend/websocket/Dockerfile b/backend/websocket/Dockerfile deleted file mode 100644 index df939d1..0000000 --- a/backend/websocket/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -FROM openjdk:17-jdk-slim - -LABEL maintainer="emotion-museum" - -# 设置时区 -ENV TZ=Asia/Shanghai -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - -# 创建应用目录 -WORKDIR /app - -# 复制jar文件 -COPY target/emotion-websocket-1.0.0.jar app.jar - -# 创建日志目录 -RUN mkdir -p /app/logs - -# 暴露端口 -EXPOSE 19007 - -# 启动应用 -ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=prod", "app.jar"] diff --git a/backend/websocket/README.md b/backend/websocket/README.md deleted file mode 100644 index dabd048..0000000 --- a/backend/websocket/README.md +++ /dev/null @@ -1,268 +0,0 @@ -# Emotion WebSocket 聊天服务 - -## 概述 - -emotion-websocket 是情绪博物馆项目的WebSocket聊天微服务,提供实时聊天功能,支持用户与AI的实时对话。 - -## 功能特性 - -- ✅ WebSocket实时通信 -- ✅ 用户与AI实时对话 -- ✅ 会话管理 -- ✅ 消息状态跟踪 -- ✅ 心跳检测 -- ✅ 在线用户管理 -- ✅ 消息广播 -- ✅ 异步AI响应处理 - -## 技术栈 - -- Spring Boot 3.0.2 -- Spring WebSocket -- STOMP协议 -- SockJS -- Spring Cloud Alibaba -- Nacos服务发现 -- OpenFeign服务调用 -- MyBatis Plus -- MySQL -- Redis - -## 端口配置 - -- 服务端口: 19007 -- WebSocket端点: `/ws/chat` - -## API接口 - -### WebSocket端点 - -``` -ws://localhost:19007/ws/chat -``` - -### STOMP消息映射 - -- `/app/chat.send` - 发送聊天消息 -- `/app/chat.connect` - 用户连接 -- `/app/chat.disconnect` - 用户断开连接 -- `/app/chat.heartbeat` - 心跳检测 - -### 订阅端点 - -- `/user/queue/messages` - 用户私有消息 -- `/topic/conversation/{conversationId}` - 会话消息 -- `/topic/broadcast` - 广播消息 - -### REST API - -#### 发送测试消息 -```http -POST /websocket/send?userId={userId}&message={message} -``` - -#### 广播测试消息 -```http -POST /websocket/broadcast?message={message} -``` - -#### 获取在线用户 -```http -GET /websocket/online-users -``` - -## 消息格式 - -### 聊天请求 (ChatRequest) -```json -{ - "conversationId": "会话ID", - "content": "消息内容", - "senderId": "发送者ID", - "senderType": "USER|GUEST|AI|SYSTEM", - "messageType": "TEXT|TYPING|SYSTEM|ERROR|HEARTBEAT|CONNECTION|AI_THINKING" -} -``` - -### WebSocket消息 (WebSocketMessage) -```json -{ - "messageId": "消息ID", - "conversationId": "会话ID", - "type": "TEXT|TYPING|SYSTEM|ERROR|HEARTBEAT|CONNECTION|AI_THINKING", - "content": "消息内容", - "senderId": "发送者ID", - "senderType": "USER|GUEST|AI|SYSTEM", - "status": "SENDING|SENT|DELIVERED|READ|FAILED", - "createTime": "2025-07-17 15:30:00", - "data": {} -} -``` - -## 启动方式 - -### 本地开发启动 - -1. 确保MySQL和Redis服务已启动 -2. 确保Nacos服务已启动 -3. 启动emotion-ai服务(WebSocket服务依赖AI服务) - -```bash -# 进入项目根目录 -cd backend - -# 启动单个服务 -cd emotion-websocket -mvn spring-boot:run -Dspring-boot.run.profiles=local - -# 或使用统一启动脚本 -./start-services.sh -``` - -### Docker启动 - -```bash -# 构建镜像 -cd emotion-websocket -docker build -t emotion-websocket:1.0.0 . - -# 运行容器 -docker run -d \ - --name emotion-websocket \ - -p 19007:19007 \ - -e SPRING_PROFILES_ACTIVE=prod \ - emotion-websocket:1.0.0 -``` - -## 测试方法 - -### 1. 使用内置测试页面 - -访问: http://localhost:19007/websocket-test.html - -### 2. 使用JavaScript客户端 - -```javascript -// 连接WebSocket -const socket = new SockJS('http://localhost:19007/ws/chat'); -const stompClient = Stomp.over(socket); - -stompClient.connect({}, function (frame) { - console.log('Connected: ' + frame); - - // 订阅消息 - stompClient.subscribe('/user/queue/messages', function (message) { - const messageData = JSON.parse(message.body); - console.log('Received:', messageData); - }); - - // 发送消息 - const chatRequest = { - content: "Hello AI!", - senderId: "test-user", - senderType: "USER", - messageType: "TEXT", - conversationId: "test-conversation" - }; - - stompClient.send("/app/chat.send", {}, JSON.stringify(chatRequest)); -}); -``` - -### 3. 使用REST API测试 - -```bash -# 发送测试消息 -curl -X POST "http://localhost:19007/websocket/send?userId=test-user&message=Hello" - -# 广播消息 -curl -X POST "http://localhost:19007/websocket/broadcast?message=System Message" - -# 查看在线用户 -curl -X GET "http://localhost:19007/websocket/online-users" -``` - -## 配置说明 - -### application.yml -```yaml -server: - port: 19007 - -spring: - application: - name: emotion-websocket -``` - -### application-local.yml -```yaml -spring: - cloud: - nacos: - discovery: - server-addr: localhost:8848 -``` - -## 日志配置 - -日志文件位置: `logs/emotion-websocket.log` - -查看日志: -```bash -tail -f logs/emotion-websocket.log -``` - -## 监控端点 - -- 健康检查: http://localhost:19007/actuator/health -- 指标监控: http://localhost:19007/actuator/metrics -- Prometheus: http://localhost:19007/actuator/prometheus - -## 注意事项 - -1. WebSocket服务依赖emotion-ai服务,请确保AI服务已启动 -2. 需要配置正确的Nacos服务发现地址 -3. 确保数据库连接配置正确 -4. 生产环境需要配置适当的跨域策略 -5. 建议配置负载均衡和会话粘性 - -## 故障排查 - -### 常见问题 - -1. **连接失败** - - 检查服务是否启动: `curl http://localhost:19007/actuator/health` - - 检查端口是否被占用: `lsof -i :19007` - -2. **AI回复失败** - - 检查emotion-ai服务是否正常 - - 查看日志中的Feign调用错误 - -3. **消息发送失败** - - 检查WebSocket连接状态 - - 查看浏览器控制台错误信息 - -### 日志级别调整 - -```yaml -logging: - level: - com.emotionmuseum.websocket: DEBUG - org.springframework.web.socket: DEBUG -``` - -## 开发指南 - -### 添加新的消息类型 - -1. 在`WebSocketMessage.MessageType`枚举中添加新类型 -2. 在`ChatWebSocketController`中添加对应的处理方法 -3. 在`ChatWebSocketServiceImpl`中实现具体逻辑 - -### 扩展功能 - -- 添加文件传输支持 -- 实现消息持久化 -- 添加消息加密 -- 实现群聊功能 -- 添加消息撤回功能 diff --git a/backend/websocket/api/pom.xml b/backend/websocket/api/pom.xml deleted file mode 100644 index dc24f15..0000000 --- a/backend/websocket/api/pom.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - websocket - 1.0.0 - ../pom.xml - - websocket-api - websocket-api - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - com.emotionmuseum - common - ${project.version} - - - diff --git a/backend/websocket/api/src/main/java/com/emotionmuseum/websocket/api/client/WebsocketClient.java b/backend/websocket/api/src/main/java/com/emotionmuseum/websocket/api/client/WebsocketClient.java deleted file mode 100644 index 3872fc3..0000000 --- a/backend/websocket/api/src/main/java/com/emotionmuseum/websocket/api/client/WebsocketClient.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.emotionmuseum.websocket.api.client; - -import com.emotionmuseum.common.result.Result; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; - -@FeignClient(name = "websocket") -public interface WebsocketClient { - - @GetMapping("/websocket/health") - Result health(); -} - - diff --git a/backend/websocket/deploy.sh b/backend/websocket/deploy.sh deleted file mode 100644 index 6ebb640..0000000 --- a/backend/websocket/deploy.sh +++ /dev/null @@ -1,226 +0,0 @@ -#!/bin/bash - -# emotion-websocket 单独部署脚本 -# 作者: emotion-museum -# 日期: 2025-07-18 - -set -e - -# 配置变量 -SERVICE_NAME="emotion-websocket" -SERVICE_PORT="19007" -REMOTE_HOST="'root@101.200.208.45'" -REMOTE_BUILD_DIR="/data/builds" -REMOTE_DOCKER_COMPOSE_DIR="/data/docker" -PROFILE="test" -PROJECT_NAME="emotion-museum" - -# 颜色输出 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -# 日志函数 -log_info() { - echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -# 检查远程服务器连接 -check_remote_connection() { - log_info "检查远程服务器连接..." - if ssh -o ConnectTimeout=10 'root@101.200.208.45' "echo 'Connection successful'" > /dev/null 2>&1; then - log_success "远程服务器连接正常" - else - log_error "无法连接到远程服务器 'root@101.200.208.45'" - exit 1 - fi -} - -# 构建服务 -build_service() { - log_info "构建服务: $SERVICE_NAME" - - # 构建父项目依赖 - cd .. - mvn clean install -DskipTests -q - cd emotion-websocket - - # 构建当前服务 - if mvn clean package -DskipTests -Ptest -q; then - log_success "服务 $SERVICE_NAME 构建成功" - else - log_error "服务 $SERVICE_NAME 构建失败" - exit 1 - fi -} - -# 创建Dockerfile -create_dockerfile() { - log_info "创建Dockerfile: $SERVICE_NAME" - - ssh 'root@101.200.208.45' "cat > $REMOTE_DOCKER_COMPOSE_DIR/Dockerfile.${SERVICE_NAME} << 'EOF' -# 使用阿里云镜像源的OpenJDK -# 使用Java 17 Alpine镜像 -FROM openjdk:17-alpine - -WORKDIR /app - -# 安装必要的工具 (Alpine Linux使用apk) -RUN apk add --no-cache curl - -COPY ${SERVICE_NAME}-1.0.0.jar app.jar - -RUN mkdir -p /app/logs - -ENV TZ=Asia/Shanghai -RUN ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone - -EXPOSE ${SERVICE_PORT} - -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \\ - CMD curl -f http://localhost:${SERVICE_PORT}/actuator/health || exit 1 - -ENTRYPOINT [\"java\", \"-Djava.security.egd=file:/dev/./urandom\", \"-Xms512m\", \"-Xmx1024m\", \"-jar\", \"app.jar\"] -EOF" -} - -# 部署服务 -deploy_service() { - log_info "开始部署服务: $SERVICE_NAME" - - # 检查jar包 - local jar_file="target/${SERVICE_NAME}-1.0.0.jar" - if [ ! -f "$jar_file" ]; then - log_error "JAR包不存在: $jar_file" - exit 1 - fi - - # 创建远程目录 - ssh 'root@101.200.208.45' " - mkdir -p $REMOTE_BUILD_DIR - mkdir -p $REMOTE_DOCKER_COMPOSE_DIR - mkdir -p /data/logs/emotion-museum - " - - # 删除旧jar包 - log_info "删除远程旧jar包" - ssh 'root@101.200.208.45' "rm -f $REMOTE_BUILD_DIR/${SERVICE_NAME}-*.jar" - - # 上传新jar包 - log_info "上传jar包" - if scp "$jar_file" 'root@101.200.208.45':$REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar; then - log_success "jar包上传成功" - else - log_error "jar包上传失败" - exit 1 - fi - - # 创建Dockerfile - create_dockerfile - - # 停止旧容器 - log_info "停止旧容器" - ssh 'root@101.200.208.45' " - docker stop ${SERVICE_NAME} 2>/dev/null || true - docker rm ${SERVICE_NAME} 2>/dev/null || true - docker rmi ${PROJECT_NAME}/${SERVICE_NAME}:latest 2>/dev/null || true - " - - # 创建Docker网络 - ssh 'root@101.200.208.45' "docker network create emotion-network 2>/dev/null || true" - - # 构建镜像 - log_info "构建Docker镜像" - ssh 'root@101.200.208.45' " - cd $REMOTE_DOCKER_COMPOSE_DIR - # 复制jar包到Docker构建目录 - cp $REMOTE_BUILD_DIR/${SERVICE_NAME}-1.0.0.jar $REMOTE_DOCKER_COMPOSE_DIR/ - - # 构建镜像 docker build -t ${PROJECT_NAME}/${SERVICE_NAME}:latest -f Dockerfile.${SERVICE_NAME} . - - # 清理临时文件 - rm -f ${SERVICE_NAME}-1.0.0.jar " - - # 启动容器 - log_info "启动新容器" - ssh 'root@101.200.208.45' " - docker run -d \\ - --name ${SERVICE_NAME} \\ - --network emotion-network \\ - -p ${SERVICE_PORT}:${SERVICE_PORT} \\ - -v /data/logs/emotion-museum:/app/logs \\ - -e SPRING_PROFILES_ACTIVE=${PROFILE} \\ - -e MYSQL_HOST=101.200.208.45 \\ - -e MYSQL_PORT=3306 \\ - -e MYSQL_DATABASE=emotion_museum \\ - -e MYSQL_USERNAME=root \\ - -e MYSQL_PASSWORD='EmotionMuseum2025*#' \\ - -e REDIS_HOST=101.200.208.45 \\ - -e REDIS_PORT=6379 \\ - -e REDIS_PASSWORD= \\ - -e REDIS_DATABASE=0 \\ - -e NACOS_SERVER_ADDR=101.200.208.45:8848 \\ - -e NACOS_USERNAME=nacos \\ - -e NACOS_PASSWORD='Peanut2817*#' \\ - --restart unless-stopped \\ - ${PROJECT_NAME}/${SERVICE_NAME}:latest - " - - # 等待启动 - log_info "等待服务启动..." - sleep 15 - - # 检查状态 - if ssh 'root@101.200.208.45' "docker ps | grep ${SERVICE_NAME}" > /dev/null; then - log_success "服务启动成功" - - # 显示日志 - log_info "服务日志 最后20行:" - ssh 'root@101.200.208.45' "docker logs --tail 20 ${SERVICE_NAME}" - - # 健康检查 - log_info "执行健康检查..." - sleep 10 - if ssh 'root@101.200.208.45' "curl -f -s http://localhost:${SERVICE_PORT}/actuator/health" > /dev/null 2>&1; then - log_success "健康检查通过" - else - log_warning "健康检查失败,服务可能仍在启动中" - fi - else - log_error "服务启动失败" - ssh 'root@101.200.208.45' "docker logs ${SERVICE_NAME}" - exit 1 - fi -} - -# 主函数 -main() { - log_info "开始部署 $SERVICE_NAME 服务" - log_info "目标服务器: $REMOTE_HOST" - log_info "服务端口: $SERVICE_PORT" - log_info "部署环境: $PROFILE" - - check_remote_connection - build_service - deploy_service - - log_success "$SERVICE_NAME 服务部署完成!" - log_info "访问地址: http://101.200.208.45:$SERVICE_PORT" -} - -# 执行主函数 -main "$@" diff --git a/backend/websocket/pom.xml b/backend/websocket/pom.xml deleted file mode 100644 index c1079b4..0000000 --- a/backend/websocket/pom.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - 4.0.0 - - com.emotionmuseum - backend - 1.0.0 - ../pom.xml - - websocket - pom - - api - server - - \ No newline at end of file diff --git a/backend/websocket/server/pom.xml b/backend/websocket/server/pom.xml deleted file mode 100644 index 260be41..0000000 --- a/backend/websocket/server/pom.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - 4.0.0 - - - com.emotionmuseum - websocket - 1.0.0 - - - websocket-server - websocket - WebSocket聊天服务 - - - - com.emotionmuseum - websocket-api - ${project.version} - - - com.emotionmuseum - common - ${project.version} - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-websocket - - - org.springframework.boot - spring-boot-starter-validation - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - org.springframework.cloud - spring-cloud-starter-loadbalancer - - - mysql - mysql-connector-java - - - com.alibaba - druid-spring-boot-starter - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-discovery - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-config - - - org.springframework.boot - spring-boot-starter-data-redis - - - org.springframework.boot - spring-boot-starter-actuator - - - io.micrometer - micrometer-registry-prometheus - - - \ No newline at end of file diff --git a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/WebsocketApplication.java b/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/WebsocketApplication.java deleted file mode 100644 index f9cd873..0000000 --- a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/WebsocketApplication.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.emotionmuseum.websocket; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; -import org.springframework.cloud.openfeign.EnableFeignClients; - -/** - * WebSocket聊天服务启动类 - */ -@SpringBootApplication -@EnableDiscoveryClient -@EnableFeignClients(basePackages = "com.emotionmuseum") -public class WebsocketApplication { - - public static void main(String[] args) { - SpringApplication.run(WebsocketApplication.class, args); - } -} diff --git a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/config/AsyncConfig.java b/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/config/AsyncConfig.java deleted file mode 100644 index d4d86e6..0000000 --- a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/config/AsyncConfig.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.emotionmuseum.websocket.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; - -import java.util.concurrent.Executor; - -/** - * 异步配置类 - */ -@Configuration -@EnableAsync -public class AsyncConfig { - - /** - * 配置异步任务执行器 - * @return 任务执行器 - */ - @Bean(name = "taskExecutor") - public Executor taskExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(10); - executor.setMaxPoolSize(50); - executor.setQueueCapacity(200); - executor.setThreadNamePrefix("websocket-async-"); - executor.setKeepAliveSeconds(60); - executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy()); - executor.initialize(); - return executor; - } -} diff --git a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/config/WebSocketConfig.java b/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/config/WebSocketConfig.java deleted file mode 100644 index e50d196..0000000 --- a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/config/WebSocketConfig.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.emotionmuseum.websocket.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.messaging.simp.config.MessageBrokerRegistry; -import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; -import org.springframework.web.socket.config.annotation.StompEndpointRegistry; -import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; - -/** - * WebSocket配置类 - * 用于配置WebSocket消息代理和端点 - */ -@Configuration -@EnableWebSocketMessageBroker -public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { - - /** - * 配置消息代理 - * @param config 消息代理注册器 - */ - @Override - public void configureMessageBroker(MessageBrokerRegistry config) { - // 启用简单消息代理,用于向客户端发送消息 - config.enableSimpleBroker("/topic", "/queue"); - // 设置应用程序目的地前缀,客户端发送消息时使用 - config.setApplicationDestinationPrefixes("/app"); - // 设置用户目的地前缀,用于点对点消息 - config.setUserDestinationPrefix("/user"); - } - - /** - * 注册STOMP端点 - * @param registry STOMP端点注册器 - */ - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - // 注册WebSocket端点,允许跨域访问 - registry.addEndpoint("/ws/chat") - .setAllowedOriginPatterns("*") - .withSockJS(); - - // 注册原生WebSocket端点(不使用SockJS) - registry.addEndpoint("/ws/chat") - .setAllowedOriginPatterns("*"); - } -} diff --git a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/controller/ChatWebSocketController.java b/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/controller/ChatWebSocketController.java deleted file mode 100644 index bc90014..0000000 --- a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/controller/ChatWebSocketController.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.emotionmuseum.websocket.controller; - -import com.emotionmuseum.websocket.dto.ChatRequest; -import com.emotionmuseum.websocket.dto.WebSocketMessage; -import com.emotionmuseum.websocket.service.ChatWebSocketService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.messaging.handler.annotation.MessageMapping; -import org.springframework.messaging.handler.annotation.Payload; -import org.springframework.messaging.simp.SimpMessageHeaderAccessor; -import org.springframework.stereotype.Controller; - -import java.security.Principal; - -/** - * WebSocket聊天控制器 - */ -@Slf4j -@Controller -@RequiredArgsConstructor -public class ChatWebSocketController { - - private final ChatWebSocketService chatWebSocketService; - - /** - * 处理聊天消息 - * @param chatRequest 聊天请求 - * @param headerAccessor 消息头访问器 - * @param principal 用户主体 - */ - @MessageMapping("/chat.send") - public void sendMessage(@Payload ChatRequest chatRequest, - SimpMessageHeaderAccessor headerAccessor, - Principal principal) { - try { - log.info("收到WebSocket聊天消息: {}", chatRequest); - - // 获取会话ID - String sessionId = headerAccessor.getSessionId(); - - // 处理聊天消息 - chatWebSocketService.handleChatMessage(chatRequest, sessionId, principal); - - } catch (Exception e) { - log.error("处理WebSocket聊天消息失败", e); - - // 发送错误消息 - WebSocketMessage errorMessage = WebSocketMessage.builder() - .type(WebSocketMessage.MessageType.ERROR) - .content("消息发送失败: " + e.getMessage()) - .senderType(WebSocketMessage.SenderType.SYSTEM) - .status(WebSocketMessage.MessageStatus.FAILED) - .build(); - - chatWebSocketService.sendMessageToUser(chatRequest.getSenderId(), errorMessage); - } - } - - /** - * 处理用户连接 - * @param headerAccessor 消息头访问器 - * @param principal 用户主体 - */ - @MessageMapping("/chat.connect") - public void connectUser(SimpMessageHeaderAccessor headerAccessor, Principal principal) { - try { - String sessionId = headerAccessor.getSessionId(); - log.info("用户连接WebSocket: sessionId={}, principal={}", sessionId, principal); - - chatWebSocketService.handleUserConnect(sessionId, principal); - - } catch (Exception e) { - log.error("处理用户WebSocket连接失败", e); - } - } - - /** - * 处理用户断开连接 - * @param headerAccessor 消息头访问器 - * @param principal 用户主体 - */ - @MessageMapping("/chat.disconnect") - public void disconnectUser(SimpMessageHeaderAccessor headerAccessor, Principal principal) { - try { - String sessionId = headerAccessor.getSessionId(); - log.info("用户断开WebSocket连接: sessionId={}, principal={}", sessionId, principal); - - chatWebSocketService.handleUserDisconnect(sessionId, principal); - - } catch (Exception e) { - log.error("处理用户WebSocket断开连接失败", e); - } - } - - /** - * 处理心跳消息 - * @param headerAccessor 消息头访问器 - * @param principal 用户主体 - */ - @MessageMapping("/chat.heartbeat") - public void heartbeat(SimpMessageHeaderAccessor headerAccessor, Principal principal) { - try { - String sessionId = headerAccessor.getSessionId(); - - // 发送心跳响应 - WebSocketMessage heartbeatMessage = WebSocketMessage.builder() - .type(WebSocketMessage.MessageType.HEARTBEAT) - .content("pong") - .senderType(WebSocketMessage.SenderType.SYSTEM) - .status(WebSocketMessage.MessageStatus.SENT) - .build(); - - String userId = principal != null ? principal.getName() : sessionId; - chatWebSocketService.sendMessageToUser(userId, heartbeatMessage); - - } catch (Exception e) { - log.error("处理WebSocket心跳失败", e); - } - } -} diff --git a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/controller/WebSocketTestController.java b/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/controller/WebSocketTestController.java deleted file mode 100644 index 33b447d..0000000 --- a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/controller/WebSocketTestController.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.emotionmuseum.websocket.controller; - -import com.emotionmuseum.common.result.Result; -import com.emotionmuseum.websocket.dto.WebSocketMessage; -import com.emotionmuseum.websocket.manager.WebSocketSessionManager; -import com.emotionmuseum.websocket.service.ChatWebSocketService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.*; - -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -/** - * WebSocket测试控制器 - */ -@Slf4j -@RestController -@RequestMapping("/websocket") -@RequiredArgsConstructor -public class WebSocketTestController { - - private final ChatWebSocketService chatWebSocketService; - private final WebSocketSessionManager sessionManager; - - /** - * 发送测试消息 - * @param userId 用户ID - * @param message 消息内容 - * @return 结果 - */ - @PostMapping("/send") - public Result> sendTestMessage(@RequestParam String userId, @RequestParam String message) { - try { - log.info("发送测试消息: userId={}, message={}", userId, message); - - // 创建测试消息 - WebSocketMessage testMessage = WebSocketMessage.builder() - .messageId(UUID.randomUUID().toString()) - .type(WebSocketMessage.MessageType.TEXT) - .content(message) - .senderId("system") - .senderType(WebSocketMessage.SenderType.SYSTEM) - .status(WebSocketMessage.MessageStatus.SENT) - .createTime(LocalDateTime.now()) - .build(); - - // 发送消息 - chatWebSocketService.sendMessageToUser(userId, testMessage); - - Map result = new HashMap<>(); - result.put("success", true); - result.put("messageId", testMessage.getMessageId()); - result.put("timestamp", testMessage.getCreateTime()); - - return Result.success(result); - - } catch (Exception e) { - log.error("发送测试消息失败", e); - return Result.error("发送测试消息失败: " + e.getMessage()); - } - } - - /** - * 广播测试消息 - * @param message 消息内容 - * @return 结果 - */ - @PostMapping("/broadcast") - public Result> broadcastTestMessage(@RequestParam String message) { - try { - log.info("广播测试消息: message={}", message); - - // 创建广播消息 - WebSocketMessage broadcastMessage = WebSocketMessage.builder() - .messageId(UUID.randomUUID().toString()) - .type(WebSocketMessage.MessageType.SYSTEM) - .content(message) - .senderId("system") - .senderType(WebSocketMessage.SenderType.SYSTEM) - .status(WebSocketMessage.MessageStatus.SENT) - .createTime(LocalDateTime.now()) - .build(); - - // 广播消息 - chatWebSocketService.broadcastMessage(broadcastMessage); - - Map result = new HashMap<>(); - result.put("success", true); - result.put("messageId", broadcastMessage.getMessageId()); - result.put("timestamp", broadcastMessage.getCreateTime()); - - return Result.success(result); - - } catch (Exception e) { - log.error("广播测试消息失败", e); - return Result.error("广播测试消息失败: " + e.getMessage()); - } - } - - /** - * 获取在线用户信息 - * @return 在线用户信息 - */ - @GetMapping("/online-users") - public Result> getOnlineUsers() { - try { - Map result = new HashMap<>(); - result.put("count", sessionManager.getOnlineUserCount()); - result.put("users", sessionManager.getOnlineUserIds()); - - return Result.success(result); - - } catch (Exception e) { - log.error("获取在线用户信息失败", e); - return Result.error("获取在线用户信息失败: " + e.getMessage()); - } - } -} diff --git a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/dto/ChatRequest.java b/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/dto/ChatRequest.java deleted file mode 100644 index 255c714..0000000 --- a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/dto/ChatRequest.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.emotionmuseum.websocket.dto; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; - -/** - * 聊天请求DTO - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class ChatRequest { - - /** - * 会话ID - */ - private String conversationId; - - /** - * 消息内容 - */ - @NotBlank(message = "消息内容不能为空") - @Size(max = 2000, message = "消息内容不能超过2000字符") - private String content; - - /** - * 发送者ID(用户ID或guest标识) - */ - private String senderId; - - /** - * 发送者类型 - */ - private WebSocketMessage.SenderType senderType; - - /** - * 消息类型 - */ - private WebSocketMessage.MessageType messageType; -} diff --git a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/dto/WebSocketMessage.java b/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/dto/WebSocketMessage.java deleted file mode 100644 index bafbf64..0000000 --- a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/dto/WebSocketMessage.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.emotionmuseum.websocket.dto; - -import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.time.LocalDateTime; - -/** - * WebSocket消息DTO - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class WebSocketMessage { - - /** - * 消息ID - */ - private String messageId; - - /** - * 会话ID - */ - private String conversationId; - - /** - * 消息类型 - */ - private MessageType type; - - /** - * 消息内容 - */ - private String content; - - /** - * 发送者ID(用户ID或guest标识) - */ - private String senderId; - - /** - * 发送者类型 - */ - private SenderType senderType; - - /** - * 消息状态 - */ - private MessageStatus status; - - /** - * 创建时间 - */ - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; - - /** - * 额外数据 - */ - private Object data; - - /** - * 消息类型枚举 - */ - public enum MessageType { - TEXT, // 文本消息 - TYPING, // 正在输入 - SYSTEM, // 系统消息 - ERROR, // 错误消息 - HEARTBEAT, // 心跳消息 - CONNECTION, // 连接状态 - AI_THINKING // AI思考中 - } - - /** - * 发送者类型枚举 - */ - public enum SenderType { - USER, // 用户 - GUEST, // 游客 - AI, // AI - SYSTEM // 系统 - } - - /** - * 消息状态枚举 - */ - public enum MessageStatus { - SENDING, // 发送中 - SENT, // 已发送 - DELIVERED, // 已送达 - READ, // 已读 - FAILED // 发送失败 - } -} diff --git a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/feign/AiServiceClient.java b/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/feign/AiServiceClient.java deleted file mode 100644 index 3df382b..0000000 --- a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/feign/AiServiceClient.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.emotionmuseum.websocket.feign; - -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; - -import java.util.Map; - -/** - * AI服务Feign客户端 - */ -@FeignClient(name = "emotion-ai") -public interface AiServiceClient { - - /** - * 调用AI聊天接口 - * @param requestBody 请求体 - * @return AI响应 - */ - @PostMapping("/api/ai/chat/send") - Map chat(@RequestBody Map requestBody); - - /** - * 调用游客聊天接口 - * @param requestBody 请求体 - * @return AI响应 - */ - @PostMapping("/api/ai/guest/chat") - Map guestChat(@RequestBody Map requestBody); -} diff --git a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/listener/WebSocketEventListener.java b/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/listener/WebSocketEventListener.java deleted file mode 100644 index bcf9002..0000000 --- a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/listener/WebSocketEventListener.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.emotionmuseum.websocket.listener; - -import com.emotionmuseum.websocket.manager.WebSocketSessionManager; -import com.emotionmuseum.websocket.service.ChatWebSocketService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.event.EventListener; -import org.springframework.messaging.simp.stomp.StompHeaderAccessor; -import org.springframework.stereotype.Component; -import org.springframework.web.socket.messaging.SessionConnectedEvent; -import org.springframework.web.socket.messaging.SessionDisconnectEvent; -import org.springframework.web.socket.messaging.SessionSubscribeEvent; -import org.springframework.web.socket.messaging.SessionUnsubscribeEvent; - -import java.security.Principal; - -/** - * WebSocket事件监听器 - */ -@Slf4j -@Component -@RequiredArgsConstructor -public class WebSocketEventListener { - - private final WebSocketSessionManager sessionManager; - private final ChatWebSocketService chatWebSocketService; - - /** - * 监听WebSocket连接事件 - * @param event 连接事件 - */ - @EventListener - public void handleWebSocketConnectListener(SessionConnectedEvent event) { - StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage()); - String sessionId = headerAccessor.getSessionId(); - Principal principal = headerAccessor.getUser(); - - log.info("收到WebSocket连接事件: sessionId={}, principal={}", sessionId, principal); - - // 处理用户连接 - chatWebSocketService.handleUserConnect(sessionId, principal); - } - - /** - * 监听WebSocket断开连接事件 - * @param event 断开连接事件 - */ - @EventListener - public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) { - StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage()); - String sessionId = headerAccessor.getSessionId(); - Principal principal = headerAccessor.getUser(); - - log.info("收到WebSocket断开连接事件: sessionId={}, principal={}", sessionId, principal); - - // 处理用户断开连接 - chatWebSocketService.handleUserDisconnect(sessionId, principal); - } - - /** - * 监听WebSocket订阅事件 - * @param event 订阅事件 - */ - @EventListener - public void handleWebSocketSubscribeListener(SessionSubscribeEvent event) { - StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage()); - String sessionId = headerAccessor.getSessionId(); - String destination = headerAccessor.getDestination(); - - log.info("收到WebSocket订阅事件: sessionId={}, destination={}", sessionId, destination); - } - - /** - * 监听WebSocket取消订阅事件 - * @param event 取消订阅事件 - */ - @EventListener - public void handleWebSocketUnsubscribeListener(SessionUnsubscribeEvent event) { - StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage()); - String sessionId = headerAccessor.getSessionId(); - String destination = headerAccessor.getDestination(); - - log.info("收到WebSocket取消订阅事件: sessionId={}, destination={}", sessionId, destination); - } -} diff --git a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/manager/WebSocketSessionManager.java b/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/manager/WebSocketSessionManager.java deleted file mode 100644 index 89bda14..0000000 --- a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/manager/WebSocketSessionManager.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.emotionmuseum.websocket.manager; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.Set; - -/** - * WebSocket会话管理器 - * 用于管理WebSocket连接会话 - */ -@Slf4j -@Component -public class WebSocketSessionManager { - - /** - * 存储用户ID与会话ID的映射关系 - * key: userId, value: sessionId - */ - private final ConcurrentMap userSessionMap = new ConcurrentHashMap<>(); - - /** - * 存储会话ID与用户ID的映射关系 - * key: sessionId, value: userId - */ - private final ConcurrentMap sessionUserMap = new ConcurrentHashMap<>(); - - /** - * 存储会话ID与会话信息的映射关系 - * key: sessionId, value: SessionInfo - */ - private final ConcurrentMap sessionInfoMap = new ConcurrentHashMap<>(); - - /** - * 添加会话 - * @param userId 用户ID - * @param sessionId 会话ID - * @param conversationId 对话ID - */ - public void addSession(String userId, String sessionId, String conversationId) { - // 如果用户已有会话,先移除旧会话 - String oldSessionId = userSessionMap.get(userId); - if (oldSessionId != null) { - removeSession(oldSessionId); - } - - userSessionMap.put(userId, sessionId); - sessionUserMap.put(sessionId, userId); - sessionInfoMap.put(sessionId, new SessionInfo(userId, sessionId, conversationId, System.currentTimeMillis())); - - log.info("WebSocket会话已添加: userId={}, sessionId={}, conversationId={}", userId, sessionId, conversationId); - } - - /** - * 移除会话 - * @param sessionId 会话ID - */ - public void removeSession(String sessionId) { - String userId = sessionUserMap.remove(sessionId); - if (userId != null) { - userSessionMap.remove(userId); - sessionInfoMap.remove(sessionId); - log.info("WebSocket会话已移除: userId={}, sessionId={}", userId, sessionId); - } - } - - /** - * 根据用户ID获取会话ID - * @param userId 用户ID - * @return 会话ID - */ - public String getSessionIdByUserId(String userId) { - return userSessionMap.get(userId); - } - - /** - * 根据会话ID获取用户ID - * @param sessionId 会话ID - * @return 用户ID - */ - public String getUserIdBySessionId(String sessionId) { - return sessionUserMap.get(sessionId); - } - - /** - * 根据会话ID获取会话信息 - * @param sessionId 会话ID - * @return 会话信息 - */ - public SessionInfo getSessionInfo(String sessionId) { - return sessionInfoMap.get(sessionId); - } - - /** - * 检查用户是否在线 - * @param userId 用户ID - * @return 是否在线 - */ - public boolean isUserOnline(String userId) { - return userSessionMap.containsKey(userId); - } - - /** - * 获取在线用户数量 - * @return 在线用户数量 - */ - public int getOnlineUserCount() { - return userSessionMap.size(); - } - - /** - * 获取所有在线用户ID - * @return 在线用户ID集合 - */ - public Set getOnlineUserIds() { - return userSessionMap.keySet(); - } - - /** - * 会话信息内部类 - */ - public static class SessionInfo { - private final String userId; - private final String sessionId; - private final String conversationId; - private final long connectTime; - - public SessionInfo(String userId, String sessionId, String conversationId, long connectTime) { - this.userId = userId; - this.sessionId = sessionId; - this.conversationId = conversationId; - this.connectTime = connectTime; - } - - // Getters - public String getUserId() { return userId; } - public String getSessionId() { return sessionId; } - public String getConversationId() { return conversationId; } - public long getConnectTime() { return connectTime; } - } -} diff --git a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/service/AiChatService.java b/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/service/AiChatService.java deleted file mode 100644 index c7f11b9..0000000 --- a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/service/AiChatService.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.emotionmuseum.websocket.service; - -import java.util.concurrent.CompletableFuture; - -/** - * AI聊天服务接口 - */ -public interface AiChatService { - - /** - * 异步获取AI聊天响应 - * @param message 用户消息 - * @param conversationId 会话ID - * @param userId 用户ID - * @return AI回复的CompletableFuture - */ - CompletableFuture getChatResponseAsync(String message, String conversationId, String userId); - - /** - * 同步获取AI聊天响应 - * @param message 用户消息 - * @param conversationId 会话ID - * @param userId 用户ID - * @return AI回复 - */ - String getChatResponse(String message, String conversationId, String userId); -} diff --git a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/service/ChatWebSocketService.java b/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/service/ChatWebSocketService.java deleted file mode 100644 index 4b2cb5d..0000000 --- a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/service/ChatWebSocketService.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.emotionmuseum.websocket.service; - -import com.emotionmuseum.websocket.dto.ChatRequest; -import com.emotionmuseum.websocket.dto.WebSocketMessage; - -import java.security.Principal; - -/** - * WebSocket聊天服务接口 - */ -public interface ChatWebSocketService { - - /** - * 处理聊天消息 - * @param chatRequest 聊天请求 - * @param sessionId 会话ID - * @param principal 用户主体 - */ - void handleChatMessage(ChatRequest chatRequest, String sessionId, Principal principal); - - /** - * 处理用户连接 - * @param sessionId 会话ID - * @param principal 用户主体 - */ - void handleUserConnect(String sessionId, Principal principal); - - /** - * 处理用户断开连接 - * @param sessionId 会话ID - * @param principal 用户主体 - */ - void handleUserDisconnect(String sessionId, Principal principal); - - /** - * 向用户发送消息 - * @param userId 用户ID - * @param message 消息 - */ - void sendMessageToUser(String userId, WebSocketMessage message); - - /** - * 向会话发送消息 - * @param conversationId 会话ID - * @param message 消息 - */ - void sendMessageToConversation(String conversationId, WebSocketMessage message); - - /** - * 向所有用户广播消息 - * @param message 消息 - */ - void broadcastMessage(WebSocketMessage message); - - /** - * 发送AI回复消息 - * @param userId 用户ID - * @param conversationId 会话ID - * @param aiReply AI回复内容 - */ - void sendAiReplyMessage(String userId, String conversationId, String aiReply); -} diff --git a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/service/impl/AiChatServiceImpl.java b/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/service/impl/AiChatServiceImpl.java deleted file mode 100644 index d225746..0000000 --- a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/service/impl/AiChatServiceImpl.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.emotionmuseum.websocket.service.impl; - -import com.emotionmuseum.websocket.feign.AiServiceClient; -import com.emotionmuseum.websocket.service.AiChatService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.CompletableFuture; - -/** - * AI聊天服务实现类 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class AiChatServiceImpl implements AiChatService { - - private final AiServiceClient aiServiceClient; - - @Async - @Override - public CompletableFuture getChatResponseAsync(String message, String conversationId, String userId) { - try { - String response = getChatResponse(message, conversationId, userId); - return CompletableFuture.completedFuture(response); - } catch (Exception e) { - log.error("异步获取AI聊天响应失败", e); - CompletableFuture future = new CompletableFuture<>(); - future.completeExceptionally(e); - return future; - } - } - - @Override - public String getChatResponse(String message, String conversationId, String userId) { - try { - log.info("调用AI服务获取聊天响应: message={}, conversationId={}, userId={}", message, conversationId, userId); - - Map response; - - // 判断是否为游客用户 - if (userId != null && userId.startsWith("guest_")) { - // 调用游客聊天接口 - Map guestRequestBody = new HashMap<>(); - guestRequestBody.put("message", message); - guestRequestBody.put("conversationId", conversationId); - guestRequestBody.put("title", "WebSocket聊天"); - - response = aiServiceClient.guestChat(guestRequestBody); - - // 处理游客聊天响应 - if (response != null && response.containsKey("data")) { - Object data = response.get("data"); - if (data instanceof Map) { - @SuppressWarnings("unchecked") - Map dataMap = (Map) data; - if (dataMap.containsKey("aiReply")) { - return dataMap.get("aiReply").toString(); - } - } - } - } else { - // 调用注册用户聊天接口 - Map userRequestBody = new HashMap<>(); - userRequestBody.put("message", message); - userRequestBody.put("conversationId", conversationId); - userRequestBody.put("userId", userId); - userRequestBody.put("type", "text"); - userRequestBody.put("needEmotionAnalysis", false); - - response = aiServiceClient.chat(userRequestBody); - - // 处理用户聊天响应 - if (response != null && response.containsKey("data")) { - Object data = response.get("data"); - if (data instanceof Map) { - @SuppressWarnings("unchecked") - Map dataMap = (Map) data; - if (dataMap.containsKey("content")) { - return dataMap.get("content").toString(); - } - // 兼容旧格式 - if (dataMap.containsKey("aiReply")) { - return dataMap.get("aiReply").toString(); - } - } - } - } - - log.warn("AI服务返回的响应格式不正确: {}", response); - return "抱歉,AI服务暂时无法提供回复。"; - - } catch (Exception e) { - log.error("调用AI服务获取聊天响应失败", e); - return "抱歉,AI服务暂时不可用,请稍后再试。"; - } - } -} diff --git a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/service/impl/ChatWebSocketServiceImpl.java b/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/service/impl/ChatWebSocketServiceImpl.java deleted file mode 100644 index 34d3cbd..0000000 --- a/backend/websocket/server/src/main/java/com/emotionmuseum/websocket/service/impl/ChatWebSocketServiceImpl.java +++ /dev/null @@ -1,273 +0,0 @@ -package com.emotionmuseum.websocket.service.impl; - -import com.emotionmuseum.websocket.dto.ChatRequest; -import com.emotionmuseum.websocket.dto.WebSocketMessage; -import com.emotionmuseum.websocket.manager.WebSocketSessionManager; -import com.emotionmuseum.websocket.service.ChatWebSocketService; -import com.emotionmuseum.websocket.service.AiChatService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.messaging.simp.SimpMessagingTemplate; -import org.springframework.stereotype.Service; - -import java.security.Principal; -import java.time.LocalDateTime; -import java.util.UUID; - -/** - * WebSocket聊天服务实现类 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class ChatWebSocketServiceImpl implements ChatWebSocketService { - - private final SimpMessagingTemplate messagingTemplate; - private final WebSocketSessionManager sessionManager; - private final AiChatService aiChatService; - - @Override - public void handleChatMessage(ChatRequest chatRequest, String sessionId, Principal principal) { - try { - // 获取用户ID - String userId = getUserId(chatRequest, principal); - - // 获取会话信息 - WebSocketSessionManager.SessionInfo sessionInfo = sessionManager.getSessionInfo(sessionId); - String conversationId = chatRequest.getConversationId(); - - // 如果没有提供会话ID,尝试从会话管理器获取 - if (conversationId == null && sessionInfo != null) { - conversationId = sessionInfo.getConversationId(); - } - - // 如果仍然没有会话ID,为游客用户创建一个 - if (conversationId == null && userId.startsWith("guest_")) { - conversationId = "ws_conversation_" + userId + "_" + System.currentTimeMillis(); - // 更新会话管理器中的会话信息 - sessionManager.addSession(userId, sessionId, conversationId); - } - - // 创建消息 - WebSocketMessage message = WebSocketMessage.builder() - .messageId(UUID.randomUUID().toString()) - .conversationId(conversationId) - .type(chatRequest.getMessageType() != null ? chatRequest.getMessageType() : WebSocketMessage.MessageType.TEXT) - .content(chatRequest.getContent()) - .senderId(userId) - .senderType(chatRequest.getSenderType() != null ? chatRequest.getSenderType() : WebSocketMessage.SenderType.USER) - .status(WebSocketMessage.MessageStatus.SENT) - .createTime(LocalDateTime.now()) - .build(); - - // 保存用户消息到数据库 - saveUserMessage(message); - - // 向用户确认消息已收到 - sendMessageToUser(userId, message); - - // 如果是文本消息,调用AI服务获取回复 - if (message.getType() == WebSocketMessage.MessageType.TEXT) { - handleAiResponse(userId, message); - } - - } catch (Exception e) { - log.error("处理聊天消息失败: {}", e.getMessage(), e); - throw e; - } - } - - @Override - public void handleUserConnect(String sessionId, Principal principal) { - try { - String userId = principal != null ? principal.getName() : "guest_" + sessionId; - - // 为游客用户生成会话ID - String conversationId = null; - if (userId.startsWith("guest_")) { - conversationId = "ws_conversation_" + userId + "_" + System.currentTimeMillis(); - } - - // 添加会话到管理器 - sessionManager.addSession(userId, sessionId, conversationId); - - // 发送连接成功消息 - WebSocketMessage connectMessage = WebSocketMessage.builder() - .messageId(UUID.randomUUID().toString()) - .conversationId(conversationId) - .type(WebSocketMessage.MessageType.CONNECTION) - .content("WebSocket连接成功,欢迎使用情绪博物馆AI聊天服务!") - .senderId("system") - .senderType(WebSocketMessage.SenderType.SYSTEM) - .status(WebSocketMessage.MessageStatus.SENT) - .createTime(LocalDateTime.now()) - .build(); - - sendMessageToUser(userId, connectMessage); - - log.info("用户WebSocket连接成功: userId={}, sessionId={}, conversationId={}", userId, sessionId, conversationId); - - } catch (Exception e) { - log.error("处理用户连接失败: {}", e.getMessage(), e); - } - } - - @Override - public void handleUserDisconnect(String sessionId, Principal principal) { - try { - // 从会话管理器中移除会话 - sessionManager.removeSession(sessionId); - - log.info("用户WebSocket断开连接: sessionId={}", sessionId); - - } catch (Exception e) { - log.error("处理用户断开连接失败: {}", e.getMessage(), e); - } - } - - @Override - public void sendMessageToUser(String userId, WebSocketMessage message) { - try { - messagingTemplate.convertAndSendToUser(userId, "/queue/messages", message); - log.debug("向用户发送消息: userId={}, messageType={}", userId, message.getType()); - } catch (Exception e) { - log.error("向用户发送消息失败: userId={}, error={}", userId, e.getMessage(), e); - } - } - - @Override - public void sendMessageToConversation(String conversationId, WebSocketMessage message) { - try { - messagingTemplate.convertAndSend("/topic/conversation/" + conversationId, message); - log.debug("向会话发送消息: conversationId={}, messageType={}", conversationId, message.getType()); - } catch (Exception e) { - log.error("向会话发送消息失败: conversationId={}, error={}", conversationId, e.getMessage(), e); - } - } - - @Override - public void broadcastMessage(WebSocketMessage message) { - try { - messagingTemplate.convertAndSend("/topic/broadcast", message); - log.debug("广播消息: messageType={}", message.getType()); - } catch (Exception e) { - log.error("广播消息失败: error={}", e.getMessage(), e); - } - } - - @Override - public void sendAiReplyMessage(String userId, String conversationId, String aiReply) { - try { - // 分割AI回复(如果包含\n或\n\n) - String[] replyParts = aiReply.split("\\n\\n|\\n"); - - for (String part : replyParts) { - if (part.trim().isEmpty()) { - continue; - } - - WebSocketMessage aiMessage = WebSocketMessage.builder() - .messageId(UUID.randomUUID().toString()) - .conversationId(conversationId) - .type(WebSocketMessage.MessageType.TEXT) - .content(part.trim()) - .senderId("ai") - .senderType(WebSocketMessage.SenderType.AI) - .status(WebSocketMessage.MessageStatus.SENT) - .createTime(LocalDateTime.now()) - .build(); - - // 保存AI消息到数据库 - saveAiMessage(aiMessage); - - // 发送给用户 - sendMessageToUser(userId, aiMessage); - - // 短暂延迟,模拟自然对话 - Thread.sleep(500); - } - - } catch (Exception e) { - log.error("发送AI回复消息失败: userId={}, error={}", userId, e.getMessage(), e); - } - } - - /** - * 处理AI响应 - */ - private void handleAiResponse(String userId, WebSocketMessage userMessage) { - try { - // 发送AI思考中状态 - WebSocketMessage thinkingMessage = WebSocketMessage.builder() - .messageId(UUID.randomUUID().toString()) - .conversationId(userMessage.getConversationId()) - .type(WebSocketMessage.MessageType.AI_THINKING) - .content("AI正在思考中...") - .senderId("ai") - .senderType(WebSocketMessage.SenderType.AI) - .status(WebSocketMessage.MessageStatus.SENT) - .createTime(LocalDateTime.now()) - .build(); - - sendMessageToUser(userId, thinkingMessage); - - // 异步调用AI服务 - aiChatService.getChatResponseAsync(userMessage.getContent(), userMessage.getConversationId(), userId) - .thenAccept(aiReply -> { - if (aiReply != null && !aiReply.trim().isEmpty()) { - sendAiReplyMessage(userId, userMessage.getConversationId(), aiReply); - } - }) - .exceptionally(throwable -> { - log.error("AI服务调用失败", throwable); - - WebSocketMessage errorMessage = WebSocketMessage.builder() - .messageId(UUID.randomUUID().toString()) - .conversationId(userMessage.getConversationId()) - .type(WebSocketMessage.MessageType.ERROR) - .content("AI服务暂时不可用,请稍后再试") - .senderId("ai") - .senderType(WebSocketMessage.SenderType.AI) - .status(WebSocketMessage.MessageStatus.FAILED) - .createTime(LocalDateTime.now()) - .build(); - - sendMessageToUser(userId, errorMessage); - return null; - }); - - } catch (Exception e) { - log.error("处理AI响应失败", e); - } - } - - /** - * 获取用户ID - */ - private String getUserId(ChatRequest chatRequest, Principal principal) { - if (chatRequest.getSenderId() != null) { - return chatRequest.getSenderId(); - } - if (principal != null) { - return principal.getName(); - } - // 为游客用户生成一个基于时间戳的ID,保持会话期间的一致性 - return "guest_" + System.currentTimeMillis(); - } - - /** - * 保存用户消息到数据库 - */ - private void saveUserMessage(WebSocketMessage message) { - // TODO: 实现保存用户消息到数据库的逻辑 - log.debug("保存用户消息: {}", message); - } - - /** - * 保存AI消息到数据库 - */ - private void saveAiMessage(WebSocketMessage message) { - // TODO: 实现保存AI消息到数据库的逻辑 - log.debug("保存AI消息: {}", message); - } -} diff --git a/backend/websocket/server/src/main/resources/application-local.yml b/backend/websocket/server/src/main/resources/application-local.yml deleted file mode 100644 index 70218cd..0000000 --- a/backend/websocket/server/src/main/resources/application-local.yml +++ /dev/null @@ -1,85 +0,0 @@ -# 本地开发环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: localhost:8848 - namespace: - group: DEFAULT_GROUP - enabled: true - username: nacos - password: nacos - metadata: - version: 1.0.0 - zone: local - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: localhost:8848 - namespace: - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: nacos - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: 123456 - - # Redis配置 - data: - redis: - host: localhost - port: 6379 - password: - database: 0 - -# WebSocket配置 -websocket: - allowed-origins: "*" - sockjs: - enabled: true - heartbeat-time: 25000 - disconnect-delay: 5000 - stomp: - relay: - enabled: false - broker: - enabled: true - destinations: ["/topic", "/queue"] - application-destination-prefixes: ["/app"] - user-destination-prefix: "/user" - -# Coze平台配置 -coze: - base-url: https://api.coze.cn - api-key: your-coze-api-key - bot-id: 7523042446285439016 - workflow-id: 7523047462895796287 - user-id: emotion-museum-user - token: pat_GCR4qKzqpf90wMCvKsldMrB18KG3QsLDci65bZthssKsbLxu8X70BKYumleDcabO - timeout: 60 - max-retries: 3 - stream: false - -# 日志配置 -logging: - level: - com.emotionmuseum: debug - com.baomidou.mybatisplus: debug - com.alibaba.nacos: info - org.springframework.web.socket: debug - org.springframework.messaging: debug - file: - name: logs/emotion-websocket-local.log diff --git a/backend/websocket/server/src/main/resources/application-prod.yml b/backend/websocket/server/src/main/resources/application-prod.yml deleted file mode 100644 index eac833e..0000000 --- a/backend/websocket/server/src/main/resources/application-prod.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 生产环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: prod - group: DEFAULT_GROUP - enabled: true - username: nacos - password: EmotionMuseum2025 - metadata: - version: 1.0.0 - zone: prod - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: 101.200.208.45:8848 - namespace: prod - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: EmotionMuseum2025 - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://101.200.208.45:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: EmotionMuseum2025*# - - # Redis配置 - data: - redis: - host: 101.200.208.45 - port: 6379 - password: EmotionMuseum2025*# - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: warn - com.baomidou.mybatisplus: warn - com.alibaba.nacos: error - file: - name: logs/emotion-websocket-prod.log diff --git a/backend/websocket/server/src/main/resources/application-test.yml b/backend/websocket/server/src/main/resources/application-test.yml deleted file mode 100644 index 0180336..0000000 --- a/backend/websocket/server/src/main/resources/application-test.yml +++ /dev/null @@ -1,55 +0,0 @@ -# 测试环境配置 - -spring: - cloud: - nacos: - discovery: - server-addr: 101.200.208.45:8848 - namespace: test - group: DEFAULT_GROUP - enabled: true - username: nacos - password: EmotionMuseum2025 - metadata: - version: 1.0.0 - zone: test - register-enabled: true - ephemeral: true - cluster-name: DEFAULT - service: ${spring.application.name} - weight: 1 - heart-beat-interval: 5000 - heart-beat-timeout: 15000 - ip-delete-timeout: 30000 - config: - server-addr: 101.200.208.45:8848 - namespace: test - group: DEFAULT_GROUP - file-extension: yml - enabled: false - username: nacos - password: EmotionMuseum2025 - - # 数据源配置 - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://101.200.208.45:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: EmotionMuseum2025*# - - # Redis配置 - data: - redis: - host: 101.200.208.45 - port: 6379 - password: EmotionMuseum2025*# - database: 0 - -# 日志配置 -logging: - level: - com.emotionmuseum: info - com.baomidou.mybatisplus: info - com.alibaba.nacos: warn - file: - name: logs/emotion-websocket-test.log diff --git a/backend/websocket/server/src/main/resources/application.yml b/backend/websocket/server/src/main/resources/application.yml deleted file mode 100644 index 69e3961..0000000 --- a/backend/websocket/server/src/main/resources/application.yml +++ /dev/null @@ -1,88 +0,0 @@ -server: - port: 19007 - -spring: - application: - name: emotion-websocket - profiles: - active: ${SPRING_PROFILES_ACTIVE:local} - - # 数据源配置 - datasource: - type: com.alibaba.druid.pool.DruidDataSource - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true - username: root - password: EmotionMuseum2025*# - druid: - initial-size: 5 - min-idle: 5 - max-active: 20 - max-wait: 60000 - time-between-eviction-runs-millis: 60000 - min-evictable-idle-time-millis: 300000 - validation-query: SELECT 1 FROM DUAL - test-while-idle: true - test-on-borrow: false - test-on-return: false - pool-prepared-statements: true - max-pool-prepared-statement-per-connection-size: 20 - filters: stat,wall,slf4j - connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 - - # Redis配置 - redis: - host: localhost - port: 6379 - password: - database: 0 - timeout: 5000ms - lettuce: - pool: - max-active: 20 - max-idle: 10 - min-idle: 0 - max-wait: -1ms - -# MyBatis Plus配置 -mybatis-plus: - configuration: - map-underscore-to-camel-case: true - cache-enabled: false - call-setters-on-nulls: true - jdbc-type-for-null: 'null' - global-config: - db-config: - id-type: ASSIGN_ID - logic-delete-field: is_deleted - logic-delete-value: 1 - logic-not-delete-value: 0 - mapper-locations: classpath*:mapper/**/*Mapper.xml - -# 日志配置 -logging: - file: - path: /data/logs/emotion-museum/websocket - level: - com.emotionmuseum.websocket: DEBUG - org.springframework.web.socket: DEBUG - org.springframework.messaging: DEBUG - pattern: - console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n" - file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n" - file: - name: logs/emotion-websocket.log - -# 监控配置 -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus - endpoint: - health: - show-details: always - metrics: - export: - prometheus: - enabled: true diff --git a/backend/websocket/server/src/main/resources/bootstrap.yml b/backend/websocket/server/src/main/resources/bootstrap.yml deleted file mode 100644 index 5a09057..0000000 --- a/backend/websocket/server/src/main/resources/bootstrap.yml +++ /dev/null @@ -1,17 +0,0 @@ -spring: - application: - name: emotion-websocket - profiles: - active: ${SPRING_PROFILES_ACTIVE:local} - cloud: - nacos: - discovery: - server-addr: ${NACOS_SERVER_ADDR:localhost:8848} - namespace: ${NACOS_NAMESPACE:} - group: ${NACOS_GROUP:DEFAULT_GROUP} - config: - server-addr: ${NACOS_SERVER_ADDR:localhost:8848} - namespace: ${NACOS_NAMESPACE:} - group: ${NACOS_GROUP:DEFAULT_GROUP} - file-extension: yml - enabled: false diff --git a/backend/websocket/server/src/main/resources/static/websocket-test.html b/backend/websocket/server/src/main/resources/static/websocket-test.html deleted file mode 100644 index 70eff9d..0000000 --- a/backend/websocket/server/src/main/resources/static/websocket-test.html +++ /dev/null @@ -1,270 +0,0 @@ - - - - - - WebSocket聊天测试 - - - -
-

WebSocket聊天测试

- -
未连接
- -
- - - - -
- -
- -
- - -
-
- - - - - - diff --git a/backend/websocket/server/src/test/java/com/emotionmuseum/websocket/WebSocketTestApplication.java b/backend/websocket/server/src/test/java/com/emotionmuseum/websocket/WebSocketTestApplication.java deleted file mode 100644 index 676e53b..0000000 --- a/backend/websocket/server/src/test/java/com/emotionmuseum/websocket/WebSocketTestApplication.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.emotionmuseum.websocket; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; - -/** - * WebSocket应用测试类 - */ -@SpringBootTest -@ActiveProfiles("test") -class WebSocketTestApplication { - - @Test - void contextLoads() { - // 测试Spring上下文是否能正常加载 - } -} diff --git a/backend/websocket/server/src/test/resources/application-test.yml b/backend/websocket/server/src/test/resources/application-test.yml deleted file mode 100644 index 4c0741c..0000000 --- a/backend/websocket/server/src/test/resources/application-test.yml +++ /dev/null @@ -1,27 +0,0 @@ -spring: - datasource: - url: jdbc:h2:mem:testdb - driver-class-name: org.h2.Driver - username: sa - password: - - h2: - console: - enabled: true - - jpa: - hibernate: - ddl-auto: create-drop - show-sql: true - - cloud: - nacos: - discovery: - enabled: false - config: - enabled: false - -logging: - level: - com.emotionmuseum.websocket: DEBUG - org.springframework.web.socket: DEBUG