feat: 完成项目整理优化和生产环境配置

🧹 项目结构优化:
- 删除重复和过时的文件
- 整理文档到docs目录结构
- 优化配置文件到configs目录
- 创建清晰的PROJECT_STRUCTURE.md

🔧 中间件配置:
- 重启MySQL/Redis/Nacos中间件
- 使用现有数据目录,确保数据完整性
- 统一密码配置: MySQL(EmotionMuseum2025*#), Nacos(Peanut2817*#)

🌐 Nginx配置:
- 配置前端路径: /emotion-museum
- 配置API代理: /api/ -> 19000
- 配置WebSocket代理: /ws/ -> 19007
- 添加健康检查端点: /health

📋 部署脚本优化:
- restart-middleware.sh - 中间件重启脚本
- setup-nginx.sh - Nginx配置脚本
- cleanup-project.sh - 项目清理脚本
- one-click-deploy.sh - 一键部署脚本

📖 文档完善:
- DEPLOYMENT_FINAL.md - 最终部署指南
- PROJECT_STRUCTURE.md - 项目结构说明
- 完整的运维和故障排查指南

 生产环境就绪:
- 中间件: MySQL/Redis/Nacos 运行正常
- Nginx: 反向代理配置完成
- 访问地址: http://47.111.10.27/emotion-museum
- 健康检查: http://47.111.10.27/health

🎯 项目现状:
- 10个微服务模块完整
- 前后端分离架构
- 容器化部署
- 统一配置管理
- 完整的部署和运维体系
This commit is contained in:
2025-07-21 13:55:36 +08:00
parent 50c63f1b1a
commit 26f0cdd760
306 changed files with 1088 additions and 56000 deletions
-415
View File
@@ -1,415 +0,0 @@
# 情绪博物馆全栈项目 - Claude 开发指南
## 项目概述
这是一个情绪博物馆全栈项目,包含iOS客户端、Spring Boot微服务后端和Vue.js Web管理界面。项目采用现代化技术栈,实现情绪记录、AI对话、成长陪伴等核心功能。
### 项目结构
- **EmotionMuseum/**: iOS SwiftUI客户端应用
- **backend/**: Spring Cloud Alibaba微服务后端
- **web/**: Vue.js + Vite Web管理界面
- **mysql_*.sql**: 数据库脚本文件
- ***.md**: 项目文档和需求规格书
## 技术栈
### iOS客户端 (SwiftUI)
- **SwiftUI**: 声明式UI框架
- **Core Data**: 本地数据存储
- **Combine**: 响应式编程
- **MapKit**: 地图功能 (高德地图集成)
- **UIKit**: 部分复杂UI组件
### 后端微服务 (Spring Boot)
- **Spring Boot**: 3.0.2 (主框架)
- **Spring Cloud**: 2022.0.0 (微服务支持)
- **Spring Cloud Alibaba**: 2022.0.0.0 (微服务生态)
- **Java**: 17+ (编程语言)
- **Maven**: 3.6+ (依赖管理)
### Web前端 (Vue.js)
- **Vue.js**: 3.x (前端框架)
- **Vite**: 构建工具
- **Pinia**: 状态管理
- **Vue Router**: 路由管理
- **SCSS**: 样式预处理器
### 数据存储
- **MySQL**: 8.0+ (主数据库)
- **Redis**: 7.0+ (缓存)
- **MyBatis Plus**: 3.5.3.1 (ORM框架)
- **Druid**: 1.2.16 (数据库连接池)
### 微服务基础设施
- **Nacos**: 2.2.0+ (服务注册发现和配置中心)
- **Gateway**: Spring Cloud Gateway (API网关)
### 工具和库
- **Lombok**: 代码简化
- **Hutool**: 工具类库
- **FastJSON2**: JSON处理
- **JWT**: 认证授权
- **Knife4j**: API文档
- **MapStruct**: 对象映射
## 微服务架构
### 服务列表
| 服务名称 | 端口 | 描述 | 包路径 |
|---------|------|------|--------|
| emotion-gateway | 9000 | API网关 | com.emotionmuseum.gateway |
| emotion-user | 9001 | 用户服务 | com.emotionmuseum.user |
| emotion-ai | 9002 | AI对话服务 | com.emotionmuseum.ai |
| emotion-record | 9003 | 情绪记录服务 | com.emotionmuseum.record |
| emotion-growth | 9004 | 成长课题服务 | com.emotionmuseum.growth |
| emotion-explore | 9005 | 地图探索服务 | com.emotionmuseum.explore |
| emotion-reward | 9006 | 成就奖励服务 | com.emotionmuseum.reward |
| emotion-stats | 9007 | 统计分析服务 | com.emotionmuseum.stats |
| emotion-common | - | 公共模块 | com.emotionmuseum.common |
## 开发规范
### 代码结构约定
每个微服务模块遵循标准的MVC分层架构:
```
emotion-[service]/
├── sql # 数据库脚本
├── src/main/java/com/emotionmuseum/[service]/
│ ├── [Service]Application.java # 启动类
│ ├── controller/ # 控制器层
│ ├── service/ # 服务层
│ │ └── impl/ # 服务实现
│ ├── mapper/ # 数据访问层
│ ├── constants/ # 常量类
│ ├── enums/ # 枚举类
│ ├── entity/ # 实体类
│ ├── request/ # 请求体
│ ├── response/ # 响应体
│ ├── dto/ # 数据传输对象
│ ├── vo/ # 视图对象
│ └── config/ # 配置类
├── src/main/resources/
│ ├── application.yml # 配置文件
│ └── mapper/ # MyBatis XML文件
└── pom.xml # 依赖配置
```
### 命名规范
1. **包命名**: 全小写,使用点分隔
2. **类命名**: 驼峰命名法,首字母大写
3. **方法命名**: 驼峰命名法,首字母小写
4. **常量命名**: 全大写,下划线分隔
5. **数据库表名**: 小写,下划线分隔
6. **字段命名**: 小写,下划线分隔
### API设计规范
1. **RESTful风格**: 使用标准HTTP方法(GET/POST/PUT/DELETE)
2. **URL路径**: `/api/{service}/{resource}`
3. **统一响应**: 使用`Result<T>`包装所有API响应
4. **状态码**: 使用`ResultCode`枚举定义业务状态码
5. **分页**: 使用`PageQuery`进行分页查询
6. **请求体**: 所有请求封装在request中
7. **响应体**: 所有响应封装在response中
### 数据库规范
1. **表命名**: 使用模块前缀,如`user_info``ai_conversation`
2. **字段命名**: 全小写,下划线分隔
3. **主键**: 统一使用`id`,使用雪花算法生成,类型为varchar(36) UUID
4. **公共字段**: 继承`BaseEntity`,包含创建时间、更新时间、逻辑删除等
5. **逻辑删除**: 使用`deleted`字段,0=未删除,1=已删除
### 异常处理
1. **全局异常处理**: 在公共模块统一处理
2. **业务异常**: 继承`RuntimeException`
3. **异常响应**: 统一返回`Result.error()`格式
## 开发工具指令
### 自动化开发环境 (推荐)
```bash
# 启动所有服务并监控文件变化
./dev-auto.sh
# 查看服务状态
./dev-auto.sh status
# 停止所有服务
./dev-auto.sh stop
# 查看实时日志
./dev-auto.sh logs
```
**自动化开发环境特性:**
- **自动编译**: 检测Java/YAML/XML文件变化时自动重新编译
- **热重载**: 使用Spring Boot DevTools实现代码变更后自动重启
- **服务监控**: 实时监控所有微服务的健康状态
- **统一日志**: 所有服务日志统一存储在`dev-logs/`目录
- **一键管理**: 支持启动、停止、状态查看、日志查看等操作
建议安装`fswatch`以获得更好的文件监控体验:
```bash
brew install fswatch # macOS
apt-get install inotify-tools # Ubuntu
```
### 手动构建和启动
```bash
# 清理编译
mvn clean compile -DskipTests
# 编译所有模块
mvn clean install -DskipTests
# 启动单个服务
cd emotion-[service] && mvn spring-boot:run
```
### 服务操作
```bash
# 健康检查
curl http://localhost:808[x]/actuator/health
# 查看服务日志 (手动启动)
tail -f logs/emotion-[service].log
# 查看服务日志 (自动化环境)
tail -f dev-logs/emotion-[service].log
```
### iOS开发工具指令
```bash
# 在Xcode中打开项目
open EmotionMuseum/EmotionMuseum.xcodeproj
# 构建iOS应用
xcodebuild -project EmotionMuseum/EmotionMuseum.xcodeproj -scheme EmotionMuseum -configuration Debug
# 运行iOS模拟器
xcrun simctl boot "iPhone 15 Pro"
```
### Web前端工具指令
```bash
# 进入web目录
cd web
# 安装依赖
npm install
# 启动开发服务器
npm run dev
# 构建生产版本
npm run build
```
### 数据库操作
```bash
# 导入数据库结构
mysql -u root -p emotion_museum < mysql_deploy_database.sql
# 执行迁移脚本
mysql -u root -p emotion_museum < [migration_file].sql
```
## 测试规范
### 单元测试
- 使用JUnit 5和Spring Boot Test
- 测试覆盖率要求:控制器层>80%,服务层>90%
- Mock外部依赖
### 集成测试
- 使用TestContainers进行数据库测试
- 测试完整的API调用链路
### API测试
- 使用脚本文件测试API端点
- 参考`test-api.sh``test-coze-api.sh`
## 配置管理
### Nacos配置
- 环境: `emotion-dev` (开发环境)
- 配置文件: `common-mysql.yml``common-redis.yml``coze-config.yml`
- 各服务配置: `emotion-[service].yml`
### 敏感信息
- 数据库密码存储在Nacos配置中心
- API密钥使用环境变量或配置中心管理
- 生产环境配置独立管理
## 代码提交规范
### 提交信息格式
```
type(scope): description
[optional body]
[optional footer]
```
### 类型说明
- `feat`: 新功能
- `fix`: 修复bug
- `docs`: 文档修改
- `style`: 代码格式
- `refactor`: 重构
- `test`: 测试相关
- `chore`: 构建工具
### 示例
```
feat(user): add user registration API
- Add user registration endpoint
- Implement email validation
- Add password encryption
Closes #123
```
## 安全要求
1. **认证授权**: 使用JWT token进行用户认证
2. **数据加密**: 敏感数据加密存储
3. **SQL注入**: 使用参数化查询防止SQL注入
4. **XSS防护**: 对用户输入进行过滤和转义
5. **CORS配置**: 限制跨域访问
## 性能优化
1. **数据库优化**: 合理使用索引,避免N+1查询
2. **缓存策略**: 热点数据使用Redis缓存
3. **连接池**: 合理配置数据库连接池参数
4. **异步处理**: 耗时操作使用异步执行
## 监控和日志
1. **健康检查**: 所有服务提供`/actuator/health`端点
2. **Prometheus指标**: 通过`/actuator/prometheus`暴露指标
3. **日志级别**: 开发环境使用DEBUG,生产环境使用INFO
4. **日志格式**: 统一使用结构化日志
## 常见问题处理
### 服务启动失败
1. 检查Nacos服务是否启动
2. 检查数据库连接配置
3. 检查端口是否被占用
4. 查看服务日志定位错误
### 数据库问题
1. 确认数据库服务状态
2. 检查连接参数配置
3. 验证数据库权限
4. 查看慢查询日志
### 缓存问题
1. 检查Redis服务状态
2. 验证缓存配置
3. 清理过期缓存数据
## 扩展开发
### 添加新微服务
1. 复制现有服务模块结构
2. 修改包名和端口配置
3. 添加到父工程pom.xml
4. 更新启动脚本
5. 配置Nacos注册信息
### 添加新API
1. 在对应服务的controller包下创建控制器
2. 定义DTO和VO对象
3. 实现服务层逻辑
4. 添加数据访问层
5. 编写单元测试
6. 更新API文档
## 部署说明
### 开发环境
- 本地启动:使用Maven插件
- Docker部署:提供Dockerfile和docker-compose.yml
### 生产环境
- 容器化部署:使用Kubernetes
- 配置管理:环境变量和配置中心
- 服务监控:Prometheus + Grafana
## Claude 开发助手指南
### 任务执行原则
1. **准确性第一**: 在执行任何任务前,必须先分析项目结构和现有代码,确保理解正确
2. **规范性约束**: 严格遵循项目的代码规范、命名约定和架构模式
3. **功能完整性**: 确保所有修改不会破坏现有功能,新增功能要完整实现
4. **测试验证**: 重要修改后需要运行相关测试,确保代码质量
### 文件操作规范
1. **iOS项目 (SwiftUI)**
- 修改前先读取 `EmotionMuseum/EmotionMuseum.xcodeproj/project.pbxproj` 了解项目结构
- 遵循SwiftUI声明式编程范式
- 使用现有的颜色主题和组件样式
- 新增文件要正确添加到Xcode项目中
2. **后端微服务 (Spring Boot)**
- 修改前检查 `backend/pom.xml` 了解模块依赖
- 遵循MVC分层架构
- 使用统一的Result响应格式
- 数据库操作使用MyBatis Plus
3. **Web前端 (Vue.js)**
- 修改前检查 `web/package.json` 了解依赖版本
- 使用Vue 3 Composition API
- 遵循组件化开发模式
- 样式使用SCSS预处理器
### 常用检查清单
#### 开始任务前
- [ ] 读取相关文档 (*.md 文件) 了解需求
- [ ] 分析现有代码结构和实现模式
- [ ] 确认修改范围和影响面
- [ ] 检查是否有相关测试需要更新
#### 完成任务后
- [ ] 代码符合项目规范
- [ ] 新增功能完整可用
- [ ] 没有破坏现有功能
- [ ] 相关文档已更新
- [ ] 提交信息规范
### 错误预防
1. **避免盲目修改**: 不要在不了解项目结构的情况下直接修改代码
2. **保持一致性**: 新增代码要与现有代码风格保持一致
3. **完整实现**: 不要留下未完成的功能或空实现
4. **谨慎删除**: 删除代码前确认没有其他地方在使用
### 紧急情况处理
如果在开发过程中遇到以下情况,需要立即停止并寻求确认:
- 发现代码存在安全风险
- 修改可能影响核心业务逻辑
- 需要修改数据库结构
- 涉及第三方API集成
---
以上是情绪博物馆全栈项目的完整开发指南,请在开发过程中严格遵循相关规范,确保代码质量和项目的可维护性。
-342
View File
@@ -1,342 +0,0 @@
# 情绪博物馆自定义目录部署指南
## 📋 部署架构
根据您的要求,本部署方案采用以下目录结构:
```
/data/
├── www/emotion-museum/ # 前端静态文件目录
│ ├── index.html
│ ├── assets/
│ └── ...
├── builds/ # 后端JAR文件目录
│ ├── emotion-gateway.jar
│ ├── emotion-ai.jar
│ └── emotion-user.jar
└── logs/emotion-museum/ # 日志目录
├── nginx/
├── gateway/
├── ai/
├── user/
├── mysql/
├── redis/
└── nacos/
```
## 🏗️ 服务架构
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 用户访问 │───▶│ Nginx │───▶│ 静态文件 │
└─────────────┘ │ (80/443) │ │ /data/www/ │
└─────────────┘ └─────────────┘
┌─────────────┐ ┌─────────────┐
│ API代理 │───▶│ Gateway │
│ /api/* │ │ (9000) │
└─────────────┘ └─────────────┘
┌─────────────┐ ┌─────────────┐
│ 微服务集群 │ │ JAR文件 │
│ AI/User/... │ │ /data/builds│
└─────────────┘ └─────────────┘
```
## 🚀 快速部署
### 1. 准备环境
```bash
# 确保Docker和Docker Compose已安装
docker --version
docker-compose --version
# 创建必要目录
sudo mkdir -p /data/www/emotion-museum
sudo mkdir -p /data/builds
sudo mkdir -p /data/logs/emotion-museum
```
### 2. 配置环境变量
```bash
# 编辑环境变量文件
vim .env
```
**配置说明**
```bash
# Coze API配置(已配置为与开发环境一致)
COZE_API_TOKEN=pat_GCR4qKzqpf90wMCvKsldMrB18KG3QsLDci65bZthssKsbLxu8X70BKYumleDcabO
# 数据库密码(可根据需要修改)
MYSQL_ROOT_PASSWORD=123456
MYSQL_PASSWORD=emotion123
```
### 3. 一键部署
```bash
# 给脚本执行权限
chmod +x deploy-custom.sh
# 执行自定义部署
./deploy-custom.sh
```
## 📁 文件说明
### 新增配置文件
1. **`docker-compose.custom.yml`** - 自定义Docker配置
- 前端文件直接从宿主机目录提供
- 后端JAR文件挂载到容器
- 日志统一保存到指定目录
2. **`deploy-custom.sh`** - 自定义部署脚本
- 自动检查和构建前后端
- 部署文件到指定目录
- 启动Docker服务
3. **`manage-custom.sh`** - 自定义管理脚本
- 服务管理和监控
- 日志查看和健康检查
- 数据备份和恢复
### 修改的配置文件
1. **`deploy/nginx/conf.d/emotion-museum.conf`** - Nginx配置
- 前端文件直接从 `/data/www/emotion-museum` 提供
- API请求代理到Docker容器内的服务
- 日志保存到 `/data/logs/emotion-museum/nginx/`
2. **`deploy/nginx/nginx.conf`** - Nginx主配置
- 更新上游服务器定义
- 支持容器间通信
## 🔧 端口配置
| 服务 | 容器端口 | 宿主机端口 | 说明 |
|------|----------|------------|------|
| Nginx | 80/443 | 80/443 | Web访问 |
| Gateway | 9000 | 9000 | API网关 |
| AI Service | 9002 | 9002 | AI服务 |
| User Service | 9001 | 9001 | 用户服务 |
| MySQL | 3306 | 3306 | 数据库 |
| Redis | 6379 | 6379 | 缓存 |
| Nacos | 8848 | 8848 | 注册中心 |
## 🛠️ 管理命令
### 服务管理
```bash
# 启动所有服务
./manage-custom.sh start
# 停止所有服务
./manage-custom.sh stop
# 重启所有服务
./manage-custom.sh restart
# 重启指定服务
./manage-custom.sh restart emotion-ai
# 查看服务状态
./manage-custom.sh status
```
### 日志管理
```bash
# 查看所有日志
./manage-custom.sh logs
# 跟踪日志输出
./manage-custom.sh logs -f
# 查看指定服务日志
./manage-custom.sh logs -s nginx
./manage-custom.sh logs -s emotion-gateway
```
### 健康检查
```bash
# 执行健康检查
./manage-custom.sh health
# 实时监控
./manage-custom.sh monitor
```
### 数据管理
```bash
# 备份数据
./manage-custom.sh backup
# 更新服务
./manage-custom.sh update
# 清理资源
./manage-custom.sh clean
```
## 📊 目录详情
### 前端目录 `/data/www/emotion-museum/`
```
/data/www/emotion-museum/
├── index.html # 主页面
├── assets/ # 静态资源
│ ├── css/ # 样式文件
│ ├── js/ # JavaScript文件
│ └── images/ # 图片文件
└── favicon.ico # 网站图标
```
### 后端目录 `/data/builds/`
```
/data/builds/
├── emotion-gateway.jar # 网关服务JAR
├── emotion-ai.jar # AI服务JAR
└── emotion-user.jar # 用户服务JAR
```
### 日志目录 `/data/logs/emotion-museum/`
```
/data/logs/emotion-museum/
├── nginx/ # Nginx日志
│ ├── access.log
│ └── error.log
├── gateway/ # 网关服务日志
├── ai/ # AI服务日志
├── user/ # 用户服务日志
├── mysql/ # MySQL日志
├── redis/ # Redis日志
└── nacos/ # Nacos日志
```
## 🔄 更新流程
### 更新前端
```bash
# 1. 重新构建前端
cd web
npm run build
# 2. 部署到目标目录
sudo rm -rf /data/www/emotion-museum/*
sudo cp -r dist/* /data/www/emotion-museum/
sudo chown -R www-data:www-data /data/www/emotion-museum
# 3. 重启Nginx(可选)
./manage-custom.sh restart nginx
```
### 更新后端
```bash
# 1. 重新构建后端
cd backend
mvn clean package -DskipTests
# 2. 部署JAR文件
sudo cp emotion-gateway/target/emotion-gateway-1.0.0.jar /data/builds/emotion-gateway.jar
sudo cp emotion-ai/target/emotion-ai-1.0.0.jar /data/builds/emotion-ai.jar
sudo cp emotion-user/target/emotion-user-1.0.0.jar /data/builds/emotion-user.jar
# 3. 重启相关服务
./manage-custom.sh restart emotion-gateway
./manage-custom.sh restart emotion-ai
./manage-custom.sh restart emotion-user
```
### 一键更新
```bash
# 自动构建和部署
./manage-custom.sh update
```
## 🚨 故障排除
### 常见问题
#### 1. 前端访问404
```bash
# 检查前端文件是否存在
ls -la /data/www/emotion-museum/
# 检查Nginx配置
./manage-custom.sh logs -s nginx
# 检查文件权限
sudo chown -R www-data:www-data /data/www/emotion-museum
```
#### 2. API调用失败
```bash
# 检查网关服务状态
./manage-custom.sh logs -s emotion-gateway
# 检查服务健康状态
curl http://localhost:9000/actuator/health
```
#### 3. 服务启动失败
```bash
# 检查JAR文件是否存在
ls -la /data/builds/
# 检查服务日志
./manage-custom.sh logs -s emotion-ai
# 检查容器状态
docker-compose -f docker-compose.custom.yml ps
```
#### 4. 日志文件过大
```bash
# 清理日志文件
sudo find /data/logs/emotion-museum -name "*.log" -size +100M -delete
# 设置日志轮转
sudo logrotate -f /etc/logrotate.conf
```
## 📞 技术支持
### 快速诊断
```bash
# 执行健康检查
./manage-custom.sh health
# 查看服务状态
./manage-custom.sh status
# 查看实时监控
./manage-custom.sh monitor
```
### 获取帮助
```bash
# 查看管理命令帮助
./manage-custom.sh --help
# 查看部署脚本帮助
./deploy-custom.sh --help
```
---
## ✅ 部署检查清单
- [ ] **目录创建**: `/data/www/emotion-museum`, `/data/builds`, `/data/logs/emotion-museum`
- [ ] **环境配置**: `COZE_API_TOKEN` 已配置为与开发环境一致
- [ ] **前端部署**: 静态文件已复制到 `/data/www/emotion-museum/`
- [ ] **后端部署**: JAR文件已复制到 `/data/builds/`
- [ ] **服务启动**: 所有Docker容器正常运行
- [ ] **访问测试**: 前端页面和API接口正常访问
- [ ] **日志检查**: 日志文件正常生成到 `/data/logs/emotion-museum/`
**🎉 恭喜!您的情绪博物馆项目已成功部署到自定义目录结构!**
-313
View File
@@ -1,313 +0,0 @@
# 情绪博物馆容器部署指南
## 📋 概述
本文档提供了情绪博物馆项目的完整容器化部署方案,支持开发环境和生产环境的快速部署。
## 🏗️ 架构说明
### 服务组件
- **前端应用** (Vue3 + Ant Design) - 端口: 80/3000
- **API网关** (Spring Cloud Gateway) - 端口: 9000
- **AI服务** (Spring Boot + Coze API) - 端口: 9002
- **用户服务** (Spring Boot) - 端口: 9001
- **MySQL数据库** - 端口: 3306
- **Redis缓存** - 端口: 6379
- **Nacos注册中心** - 端口: 8848
- **Nginx反向代理** - 端口: 80/443
### 网络架构
```
Internet → Nginx → Frontend/Gateway → Microservices → Database
```
## 🚀 快速开始
### 1. 系统要求
- **操作系统**: Linux/macOS/Windows
- **Docker**: 20.10+
- **Docker Compose**: 1.29+
- **内存**: 最少4GB,推荐8GB+
- **磁盘**: 最少10GB可用空间
### 2. 一键部署
```bash
# 克隆项目
git clone <repository-url>
cd EmotionMuseum
# 快速部署(自动安装依赖)
chmod +x quick-deploy.sh
./quick-deploy.sh
# 或者手动部署
chmod +x deploy.sh
./deploy.sh
```
### 3. 访问应用
- **前端应用**: http://localhost
- **API文档**: http://localhost:9000/doc.html
- **Nacos控制台**: http://localhost:8848/nacos (nacos/nacos)
## 📁 文件结构
```
EmotionMuseum/
├── docker-compose.yml # 开发环境配置
├── docker-compose.prod.yml # 生产环境配置
├── deploy.sh # 部署脚本
├── quick-deploy.sh # 快速部署脚本
├── manage.sh # 管理脚本
├── .env # 环境变量
├── deploy/ # 部署配置
│ ├── nginx/ # Nginx配置
│ │ ├── nginx.conf
│ │ ├── conf.d/
│ │ └── ssl/
│ ├── mysql/ # MySQL配置
│ └── redis/ # Redis配置
├── backend/ # 后端服务
│ ├── emotion-gateway/
│ │ └── Dockerfile
│ ├── emotion-ai/
│ │ └── Dockerfile
│ └── emotion-user/
│ └── Dockerfile
└── web/ # 前端应用
├── Dockerfile
└── nginx.conf
```
## ⚙️ 配置说明
### 环境变量配置
编辑 `.env` 文件:
```bash
# 数据库配置
MYSQL_ROOT_PASSWORD=123456
MYSQL_DATABASE=emotion_museum
MYSQL_USER=emotion
MYSQL_PASSWORD=emotion123
# Coze API配置
COZE_API_TOKEN=your-coze-api-token
# 时区设置
TZ=Asia/Shanghai
```
### Nginx配置
- **主配置**: `deploy/nginx/nginx.conf`
- **站点配置**: `deploy/nginx/conf.d/emotion-museum.conf`
- **SSL证书**: `deploy/nginx/ssl/`
### 数据库配置
- **MySQL配置**: `deploy/mysql/conf.d/my.cnf`
- **初始化脚本**: `backend/mysql_emotion_museum_final.sql`
## 🛠️ 管理命令
### 基础操作
```bash
# 启动所有服务
./manage.sh start
# 停止所有服务
./manage.sh stop
# 重启所有服务
./manage.sh restart
# 查看服务状态
./manage.sh status
```
### 日志管理
```bash
# 查看所有服务日志
./manage.sh logs
# 跟踪日志输出
./manage.sh logs -f
# 查看特定服务日志
./manage.sh logs -s gateway
./manage.sh logs -s ai-service
```
### 服务管理
```bash
# 重启特定服务
./manage.sh restart gateway
./manage.sh restart ai-service
# 健康检查
./manage.sh health
# 监控面板
./manage.sh monitor
```
### 数据管理
```bash
# 备份数据
./manage.sh backup
# 恢复数据
./manage.sh restore backup_file.tar.gz
# 更新服务
./manage.sh update
# 清理资源
./manage.sh clean
```
## 🔧 生产环境部署
### 1. 使用生产配置
```bash
# 使用生产环境配置文件
docker-compose -f docker-compose.prod.yml up -d
```
### 2. SSL证书配置
```bash
# 放置SSL证书
cp your-domain.crt deploy/nginx/ssl/emotion-museum.crt
cp your-domain.key deploy/nginx/ssl/emotion-museum.key
# 修改Nginx配置启用HTTPS
vim deploy/nginx/conf.d/emotion-museum.conf
```
### 3. 域名配置
修改 `deploy/nginx/conf.d/emotion-museum.conf`
```nginx
server_name your-domain.com www.your-domain.com;
```
### 4. 防火墙配置
```bash
# Ubuntu/Debian
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# CentOS/RHEL
sudo firewall-cmd --permanent --add-port=80/tcp
sudo firewall-cmd --permanent --add-port=443/tcp
sudo firewall-cmd --reload
```
## 📊 监控和维护
### 服务监控
```bash
# 实时监控
./manage.sh monitor
# 资源使用情况
docker stats
# 服务状态
docker-compose ps
```
### 日志查看
```bash
# 应用日志
./manage.sh logs -f
# 系统日志
tail -f logs/nginx/access.log
tail -f logs/mysql/error.log
```
### 性能优化
1. **数据库优化**: 调整 `deploy/mysql/conf.d/my.cnf`
2. **Redis优化**: 调整 `deploy/redis/redis.conf`
3. **Nginx优化**: 调整 `deploy/nginx/nginx.conf`
4. **JVM优化**: 修改Dockerfile中的JVM参数
## 🔒 安全配置
### 1. 数据库安全
- 修改默认密码
- 限制访问IP
- 启用SSL连接
### 2. Redis安全
- 设置密码认证
- 绑定特定IP
- 禁用危险命令
### 3. Nginx安全
- 启用HTTPS
- 配置安全头
- 限制请求频率
### 4. 应用安全
- 配置JWT密钥
- 启用CORS限制
- 设置API限流
## 🚨 故障排除
### 常见问题
#### 1. 服务启动失败
```bash
# 查看服务日志
./manage.sh logs -s service-name
# 检查端口占用
netstat -tlnp | grep :port
# 重启服务
./manage.sh restart service-name
```
#### 2. 数据库连接失败
```bash
# 检查MySQL状态
docker-compose exec mysql mysqladmin ping -u root -p
# 查看数据库日志
./manage.sh logs -s mysql
```
#### 3. 前端访问异常
```bash
# 检查Nginx配置
nginx -t
# 查看Nginx日志
./manage.sh logs -s nginx
```
#### 4. API调用失败
```bash
# 检查网关状态
curl http://localhost:9000/actuator/health
# 查看网关日志
./manage.sh logs -s gateway
```
### 性能问题
1. **内存不足**: 增加服务器内存或调整JVM参数
2. **磁盘空间**: 清理日志文件和Docker镜像
3. **网络延迟**: 检查服务间网络连接
## 📞 技术支持
如遇到问题,请:
1. 查看相关服务日志
2. 检查配置文件
3. 参考故障排除指南
4. 联系技术支持团队
---
**部署完成后,请及时修改默认密码和配置文件中的敏感信息!**
-256
View File
@@ -1,256 +0,0 @@
# 情绪博物馆项目部署指南
## 概述
本文档提供了情绪博物馆项目的完整部署指南,包括一键部署脚本的使用方法和手动部署步骤。
## 系统要求
### 本地开发环境
- Java 17+
- Maven 3.6+
- Node.js 18+
- SSH客户端
### 服务器环境
- CentOS 7/8 或 RHEL 7/8
- 最小 4GB RAM,推荐 8GB+
- 最小 50GB 磁盘空间
- Docker 支持
## 快速部署
### 1. 一键部署(推荐)
```bash
# 克隆项目
git clone <repository-url>
cd EmotionMuseum
# 执行一键部署
./deploy-final.sh all
```
### 2. 分步部署
```bash
# 1. 构建项目
./deploy-final.sh build
# 2. 配置服务器环境
./deploy-final.sh env
# 3. 配置数据库
./deploy-final.sh mysql
# 4. 配置Redis
./deploy-final.sh redis
# 5. 配置Nacos
./deploy-final.sh nacos
# 6. 上传构建产物
./deploy-final.sh upload
# 7. 导入数据库
./deploy-final.sh import-db
# 8. 部署应用服务
./deploy-final.sh deploy
# 9. 配置Nginx
./deploy-final.sh nginx
# 10. 创建密码记录
./deploy-final.sh passwords
# 11. 健康检查
./deploy-final.sh health
```
## 服务管理
### 启动/停止服务
```bash
# 查看服务状态
./deploy-final.sh status
# 启动服务
./deploy-final.sh start
# 停止服务
./deploy-final.sh stop
# 重启服务
./deploy-final.sh restart
```
### 查看日志
```bash
# 查看网关服务日志
./deploy-final.sh logs gateway
# 查看AI服务日志
./deploy-final.sh logs ai
# 查看用户服务日志
./deploy-final.sh logs user
```
## 配置说明
### 环境变量配置
主要配置文件:
- `.env.prod` - 生产环境配置
- `web/.env.production` - 前端生产环境配置
### 服务器配置
默认配置:
- 服务器IP: 47.111.10.27
- MySQL端口: 3306
- Redis端口: 6379
- Nacos端口: 8848
- 网关端口: 9000
- AI服务端口: 9002
- 用户服务端口: 9001
### 目录结构
```
/data/
├── builds/ # 应用JAR文件
├── www/ # 前端文件
│ └── emotion-museum/
│ └── web/
├── logs/ # 日志文件
│ └── emotion-museum/
│ ├── gateway/
│ ├── ai/
│ └── user/
└── programs/ # 其他程序文件
```
## 访问地址
部署完成后的访问地址:
- **前端应用**: http://47.111.10.27/emotion-museum/
- **API网关**: http://47.111.10.27:9000
- **Nacos控制台**: http://47.111.10.27:8848/nacos
## 故障排除
### 常见问题
1. **服务无法启动**
```bash
# 检查服务状态
./deploy-final.sh status
# 查看日志
./deploy-final.sh logs <service>
```
2. **数据库连接失败**
```bash
# 检查MySQL容器状态
ssh root@47.111.10.27 "docker ps | grep mysql"
# 检查数据库连接
ssh root@47.111.10.27 "docker exec emotion-mysql-prod mysql -uemotion -pEmotionDB2024! -e 'SELECT 1;'"
```
3. **前端页面无法访问**
```bash
# 检查Nginx状态
ssh root@47.111.10.27 "systemctl status nginx"
# 检查前端文件
ssh root@47.111.10.27 "ls -la /data/www/emotion-museum/web/"
```
### 日志位置
- 应用日志: `/data/logs/emotion-museum/*/app.log`
- Nginx日志: `/var/log/nginx/`
- Docker日志: `docker logs <container-name>`
## 安全建议
1. **修改默认密码**
- MySQL root密码
- 应用数据库密码
- 服务器SSH密钥
2. **配置防火墙**
```bash
# 只开放必要端口
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --permanent --add-port=8848/tcp
firewall-cmd --reload
```
3. **定期备份**
```bash
# 数据库备份
docker exec emotion-mysql-prod mysqldump -uemotion -pEmotionDB2024! emotion_museum > backup.sql
```
## 更新部署
### 应用更新
```bash
# 1. 构建新版本
./deploy-final.sh build
# 2. 停止服务
./deploy-final.sh stop
# 3. 上传新文件
./deploy-final.sh upload
# 4. 启动服务
./deploy-final.sh start
```
### 配置更新
```bash
# 重新配置Nginx
./deploy-final.sh nginx
# 重启服务
./deploy-final.sh restart
```
## 监控和维护
### 健康检查
```bash
# 执行完整健康检查
./deploy-final.sh health
```
### 性能监控
建议使用以下工具进行监控:
- Prometheus + Grafana
- ELK Stack (日志分析)
- Docker监控
## 联系支持
如遇到部署问题,请提供以下信息:
1. 错误日志
2. 系统环境信息
3. 部署步骤和配置
---
**注意**: 请确保在生产环境中修改默认密码和配置,并定期进行安全更新。
+253
View File
@@ -0,0 +1,253 @@
# 情感博物馆 - 最终部署指南
## 🎯 项目概述
情感博物馆是一个基于Spring Cloud Alibaba微服务架构的情感AI应用,包含10个微服务模块和Vue前端。
## 🏗️ 系统架构
### 后端微服务 (Spring Cloud Alibaba)
- **emotion-gateway** (19000) - API网关,统一入口
- **emotion-user** (19001) - 用户管理服务
- **emotion-ai** (19002) - AI聊天服务,集成Coze平台
- **emotion-record** (19003) - 记录管理服务
- **emotion-growth** (19004) - 成长跟踪服务
- **emotion-explore** (19005) - 探索服务
- **emotion-reward** (19006) - 奖励服务
- **emotion-websocket** (19007) - WebSocket实时通信
- **emotion-auth** (19008) - 认证授权服务
- **emotion-stats** (19009) - 统计分析服务
### 前端 (Vue + Ant Design)
- 基于Vue 3 + TypeScript + Ant Design
- 响应式设计,支持移动端
- WebSocket实时通信
- 集成AI聊天功能
### 中间件
- **MySQL 8.0** (3306) - 主数据库
- **Redis 7** (6379) - 缓存和会话存储
- **Nacos 2.2.0** (8848) - 服务注册发现和配置中心
## 🚀 快速部署
### 1. 一键部署(推荐)
```bash
# 完整部署前后端
./one-click-deploy.sh
# 仅部署后端
./one-click-deploy.sh backend
# 仅部署前端
./one-click-deploy.sh frontend
# 健康检查
./one-click-deploy.sh check
```
### 2. 中间件管理
```bash
# 重启中间件(MySQL, Redis, Nacos
./restart-middleware.sh
```
### 3. Nginx配置
```bash
# 配置Nginx反向代理
./setup-nginx.sh
```
## 📋 分步部署
### 步骤1: 准备环境
确保本地环境已安装:
- Java 17+
- Maven 3.6+
- Node.js 16+
- Docker (远程服务器)
### 步骤2: 启动中间件
```bash
./restart-middleware.sh
```
### 步骤3: 构建后端
```bash
cd backend
./build-all.sh
```
### 步骤4: 部署后端
```bash
cd backend
./deploy-remote.sh
```
### 步骤5: 部署前端
```bash
cd web-flowith
./deploy.sh
```
### 步骤6: 配置Nginx
```bash
./setup-nginx.sh
```
## 🌐 访问地址
### 生产环境
- **前端应用**: http://47.111.10.27/emotion-museum
- **API网关**: http://47.111.10.27/api/
- **WebSocket**: ws://47.111.10.27/ws/
- **健康检查**: http://47.111.10.27/health
### 管理后台
- **Nacos控制台**: http://47.111.10.27:8848/nacos
- 用户名: nacos
- 密码: Peanut2817*#
### 数据库连接
- **MySQL**: 47.111.10.27:3306
- 用户名: root
- 密码: EmotionMuseum2025*#
- 数据库: emotion_museum
- **Redis**: 47.111.10.27:6379
## 🔧 运维管理
### 查看服务状态
```bash
# 查看所有容器
ssh root@47.111.10.27 "docker ps"
# 查看特定服务日志
ssh root@47.111.10.27 "docker logs emotion-gateway --tail 50"
# 查看服务健康状态
curl http://47.111.10.27:19000/actuator/health
```
### 重启服务
```bash
# 重启单个服务
ssh root@47.111.10.27 "docker restart emotion-gateway"
# 重启所有微服务
ssh root@47.111.10.27 "docker restart \$(docker ps -q --filter name=emotion-)"
```
### 更新部署
```bash
# 更新后端服务
cd backend && ./deploy-remote.sh
# 更新前端
cd web-flowith && ./deploy.sh
# 完整更新
./one-click-deploy.sh
```
## 📊 监控和日志
### 应用日志
- 容器日志: `docker logs <service_name>`
- 应用日志: `/data/logs/emotion-museum/`
### Nginx日志
- 访问日志: `/var/log/nginx/emotion-museum.access.log`
- 错误日志: `/var/log/nginx/emotion-museum.error.log`
### 健康检查端点
- 网关: http://47.111.10.27:19000/actuator/health
- 用户服务: http://47.111.10.27:19001/actuator/health
- AI服务: http://47.111.10.27:19002/actuator/health
## 🛠️ 故障排查
### 常见问题
#### 1. 服务启动失败
```bash
# 查看容器日志
docker logs <service_name> --tail 50
# 检查端口占用
netstat -tlnp | grep <port>
# 重启服务
docker restart <service_name>
```
#### 2. 数据库连接失败
```bash
# 检查MySQL状态
docker exec emotion-mysql mysqladmin ping
# 检查数据库连接
mysql -h 47.111.10.27 -u root -p
```
#### 3. Nacos连接失败
```bash
# 检查Nacos状态
curl http://47.111.10.27:8848/nacos/v1/console/health
# 重启Nacos
docker restart emotion-nacos
```
#### 4. 前端访问404
```bash
# 检查Nginx配置
nginx -t
# 检查前端文件
ls -la /data/www/emotion-museum/
# 重载Nginx
systemctl reload nginx
```
## 📁 项目结构
```
emotion-museum/
├── 📁 backend/ # 后端微服务
├── 📁 web-flowith/ # 前端Vue项目
├── 📁 docs/ # 项目文档
├── 📁 configs/ # 配置文件
├── 🔧 one-click-deploy.sh # 一键部署脚本
├── 🔧 restart-middleware.sh # 中间件重启脚本
├── 🔧 setup-nginx.sh # Nginx配置脚本
└── 📄 DEPLOYMENT_FINAL.md # 部署指南
```
## 🔐 安全配置
### 密码管理
- MySQL root密码: EmotionMuseum2025*#
- Nacos密码: Peanut2817*#
- 所有密码已在配置文件中统一
### 网络安全
- 所有服务运行在Docker网络中
- Nginx反向代理保护内部服务
- 仅必要端口对外开放
## 📞 技术支持
如遇到问题,请:
1. 查看相关日志文件
2. 检查服务健康状态
3. 参考故障排查章节
4. 联系开发团队并提供完整日志
---
**版本**: v2.0
**更新时间**: 2025-07-21
**维护团队**: 情感博物馆开发团队
-371
View File
@@ -1,371 +0,0 @@
# 情绪博物馆完整部署指南
## 📦 部署包信息
**包名称**: `emotion-museum-1.0.0-20250713_111829.tar.gz`
**包大小**: 680KB
**SHA256**: `900d585f575b1619e74296496e2fe22f2c2e71b6ad8901d7cab82634765cc10d`
**构建时间**: 2025-07-13 11:18:29
## 🎯 部署概述
本部署包包含了情绪博物馆项目的完整容器化部署方案,支持:
- ✅ 前端Vue3应用(已构建)
- ✅ 后端微服务(Gateway、AI、User
- ✅ 数据库脚本(MySQL
- ✅ 完整的Docker配置
- ✅ 自动化部署脚本
- ✅ 监控和管理工具
## 🏗️ 系统架构
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 用户访问 │───▶│ Nginx │───▶│ 前端应用 │
└─────────────┘ │ (80/443) │ │ (3000) │
└─────────────┘ └─────────────┘
┌─────────────┐ ┌─────────────┐
│ API网关 │───▶│ 微服务集群 │
│ (9000) │ │ AI/User/... │
└─────────────┘ └─────────────┘
┌─────────────┐ ┌─────────────┐
│ MySQL │ │ Redis │
│ (3306) │ │ (6379) │
└─────────────┘ └─────────────┘
```
## 🚀 快速部署(推荐)
### 1. 下载和解压
```bash
# 下载部署包到服务器
wget https://your-domain.com/emotion-museum-1.0.0-20250713_111829.tar.gz
# 验证文件完整性
echo "900d585f575b1619e74296496e2fe22f2c2e71b6ad8901d7cab82634765cc10d emotion-museum-1.0.0-20250713_111829.tar.gz" | sha256sum -c
# 解压部署包
tar -xzf emotion-museum-1.0.0-20250713_111829.tar.gz
cd emotion-museum-1.0.0-20250713_111829
```
### 2. 配置环境变量
```bash
# 复制环境变量模板
cp .env .env.local
# 编辑配置文件
vim .env.local
```
**必须配置的项目**
```bash
# Coze API配置(必须修改)
COZE_API_TOKEN=your-actual-coze-api-token
# 数据库密码(建议修改)
MYSQL_ROOT_PASSWORD=your-secure-password
MYSQL_PASSWORD=your-secure-password
# 时区设置
TZ=Asia/Shanghai
```
### 3. 一键部署
```bash
# 给脚本执行权限
chmod +x quick-deploy.sh
# 执行一键部署(自动安装Docker等依赖)
./quick-deploy.sh
```
### 4. 验证部署
```bash
# 查看服务状态
./manage.sh status
# 健康检查
./manage.sh health
# 查看日志
./manage.sh logs
```
## 🔧 手动部署(高级用户)
### 1. 环境准备
```bash
# 安装Docker
curl -fsSL https://get.docker.com | sh
sudo systemctl start docker
sudo systemctl enable docker
# 安装Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# 添加用户到docker组
sudo usermod -aG docker $USER
```
### 2. 配置防火墙
```bash
# Ubuntu/Debian
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 8848/tcp # Nacos(可选)
# CentOS/RHEL
sudo firewall-cmd --permanent --add-port=80/tcp
sudo firewall-cmd --permanent --add-port=443/tcp
sudo firewall-cmd --permanent --add-port=8848/tcp
sudo firewall-cmd --reload
```
### 3. 部署服务
```bash
# 开发环境部署
./deploy.sh
# 或生产环境部署
docker-compose -f docker-compose.prod.yml up -d
```
## ⚙️ 配置说明
### 环境变量配置
| 变量名 | 说明 | 默认值 | 是否必须 |
|--------|------|--------|----------|
| `COZE_API_TOKEN` | Coze API令牌 | - | ✅ 必须 |
| `MYSQL_ROOT_PASSWORD` | MySQL root密码 | 123456 | 🔶 建议修改 |
| `MYSQL_PASSWORD` | MySQL用户密码 | emotion123 | 🔶 建议修改 |
| `TZ` | 时区设置 | Asia/Shanghai | ⭕ 可选 |
| `DOMAIN_NAME` | 域名(生产环境) | localhost | ⭕ 可选 |
### 端口配置
| 服务 | 端口 | 说明 |
|------|------|------|
| Nginx | 80, 443 | Web访问端口 |
| Gateway | 9000 | API网关 |
| AI Service | 9002 | AI服务 |
| User Service | 9001 | 用户服务 |
| MySQL | 3306 | 数据库 |
| Redis | 6379 | 缓存 |
| Nacos | 8848 | 注册中心 |
## 🌐 生产环境配置
### 1. HTTPS配置
```bash
# 1. 准备SSL证书
mkdir -p deploy/nginx/ssl
cp your-domain.crt deploy/nginx/ssl/emotion-museum.crt
cp your-domain.key deploy/nginx/ssl/emotion-museum.key
# 2. 修改Nginx配置
vim deploy/nginx/conf.d/emotion-museum.conf
# 取消HTTPS相关配置的注释
# 3. 重启Nginx
docker-compose restart nginx
```
### 2. 域名配置
```bash
# 修改Nginx配置中的域名
vim deploy/nginx/conf.d/emotion-museum.conf
# 将 localhost 替换为您的实际域名
```
### 3. 性能优化
```bash
# 1. 调整MySQL配置
vim deploy/mysql/conf.d/my.cnf
# 2. 调整Redis配置
vim deploy/redis/redis.conf
# 3. 调整JVM参数(在Dockerfile中)
# -Xms512m -Xmx1024m
```
## 🛠️ 管理命令
### 服务管理
```bash
./manage.sh start # 启动所有服务
./manage.sh stop # 停止所有服务
./manage.sh restart # 重启所有服务
./manage.sh restart gateway # 重启指定服务
./manage.sh status # 查看服务状态
```
### 日志管理
```bash
./manage.sh logs # 查看所有日志
./manage.sh logs -f # 跟踪日志输出
./manage.sh logs -s gateway # 查看网关日志
./manage.sh logs -s ai-service # 查看AI服务日志
```
### 数据管理
```bash
./manage.sh backup # 备份数据
./manage.sh restore backup.tar.gz # 恢复数据
./manage.sh update # 更新服务
./manage.sh clean # 清理资源
```
### 监控工具
```bash
./manage.sh monitor # 实时监控面板
./manage.sh health # 健康检查
```
## 📊 访问地址
部署完成后,您可以通过以下地址访问:
| 服务 | 地址 | 说明 |
|------|------|------|
| 前端应用 | http://localhost | 主要访问入口 |
| API文档 | http://localhost:9000/doc.html | Swagger文档 |
| Nacos控制台 | http://localhost:8848/nacos | 服务注册中心 |
| 网关健康检查 | http://localhost:9000/actuator/health | 服务状态 |
**默认账号**
- Nacos: nacos / nacos
## 🚨 故障排除
### 常见问题
#### 1. 端口冲突
```bash
# 检查端口占用
netstat -tlnp | grep :80
netstat -tlnp | grep :3306
# 解决方案:修改docker-compose.yml中的端口映射
```
#### 2. 服务启动失败
```bash
# 查看具体错误
./manage.sh logs -s service-name
# 常见原因:
# - 内存不足
# - 端口被占用
# - 配置文件错误
# - 依赖服务未启动
```
#### 3. 数据库连接失败
```bash
# 检查MySQL状态
docker-compose exec mysql mysqladmin ping -u root -p
# 检查网络连接
docker network ls
docker network inspect emotion-network
```
#### 4. 前端访问404
```bash
# 检查Nginx配置
docker-compose exec nginx nginx -t
# 检查前端容器状态
docker-compose ps web
```
#### 5. API调用失败
```bash
# 检查网关状态
curl http://localhost:9000/actuator/health
# 检查服务注册
curl http://localhost:8848/nacos/v1/ns/instance/list?serviceName=emotion-ai
```
### 性能问题
#### 1. 内存不足
```bash
# 查看内存使用
free -h
docker stats
# 解决方案:
# - 增加服务器内存
# - 调整JVM参数
# - 减少并发连接数
```
#### 2. 磁盘空间不足
```bash
# 查看磁盘使用
df -h
# 清理Docker资源
./manage.sh clean
docker system prune -a
```
#### 3. 网络延迟
```bash
# 检查服务间网络
docker-compose exec gateway ping mysql
docker-compose exec gateway ping redis
# 优化网络配置
# 使用自定义网络
# 调整网络参数
```
## 🔒 安全建议
### 1. 密码安全
- ✅ 修改所有默认密码
- ✅ 使用强密码策略
- ✅ 定期更换密码
### 2. 网络安全
- ✅ 配置防火墙规则
- ✅ 使用HTTPS加密
- ✅ 限制不必要的端口访问
### 3. 数据安全
- ✅ 定期备份数据
- ✅ 启用数据库SSL
- ✅ 配置访问控制
### 4. 应用安全
- ✅ 配置JWT密钥
- ✅ 启用API限流
- ✅ 监控异常访问
## 📞 技术支持
### 获取帮助
1. **查看文档**: 包内的 `DEPLOY.md``QUICK_START.md`
2. **查看日志**: `./manage.sh logs -f`
3. **健康检查**: `./manage.sh health`
4. **查看版本**: `cat VERSION.txt`
### 联系方式
- 📧 技术支持邮箱: support@emotion-museum.com
- 📱 技术支持QQ群: 123456789
- 🌐 官方网站: https://emotion-museum.com
---
**🎉 恭喜!您已成功部署情绪博物馆项目!**
**⚠️ 重要提醒:部署完成后请及时修改默认密码和敏感配置!**
@@ -1,575 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objects = {
/* Begin PBXContainerItemProxy section */
2FB3451A2DFBE273001A8A67 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 2FB344FF2DFBE270001A8A67 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 2FB345062DFBE270001A8A67;
remoteInfo = EmotionMuseum;
};
2FB345242DFBE273001A8A67 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 2FB344FF2DFBE270001A8A67 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 2FB345062DFBE270001A8A67;
remoteInfo = EmotionMuseum;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
2FB345072DFBE270001A8A67 /* EmotionMuseum.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = EmotionMuseum.app; sourceTree = BUILT_PRODUCTS_DIR; };
2FB345192DFBE273001A8A67 /* EmotionMuseumTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = EmotionMuseumTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
2FB345232DFBE273001A8A67 /* EmotionMuseumUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = EmotionMuseumUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
2FB345092DFBE270001A8A67 /* EmotionMuseum */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = EmotionMuseum;
sourceTree = "<group>";
};
2FB3451C2DFBE273001A8A67 /* EmotionMuseumTests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = EmotionMuseumTests;
sourceTree = "<group>";
};
2FB345262DFBE273001A8A67 /* EmotionMuseumUITests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = EmotionMuseumUITests;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
2FB345042DFBE270001A8A67 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
2FB345162DFBE273001A8A67 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
2FB345202DFBE273001A8A67 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
2FB344FE2DFBE270001A8A67 = {
isa = PBXGroup;
children = (
2FB345092DFBE270001A8A67 /* EmotionMuseum */,
2FB3451C2DFBE273001A8A67 /* EmotionMuseumTests */,
2FB345262DFBE273001A8A67 /* EmotionMuseumUITests */,
2FB345082DFBE270001A8A67 /* Products */,
);
sourceTree = "<group>";
};
2FB345082DFBE270001A8A67 /* Products */ = {
isa = PBXGroup;
children = (
2FB345072DFBE270001A8A67 /* EmotionMuseum.app */,
2FB345192DFBE273001A8A67 /* EmotionMuseumTests.xctest */,
2FB345232DFBE273001A8A67 /* EmotionMuseumUITests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
2FB345062DFBE270001A8A67 /* EmotionMuseum */ = {
isa = PBXNativeTarget;
buildConfigurationList = 2FB3452D2DFBE273001A8A67 /* Build configuration list for PBXNativeTarget "EmotionMuseum" */;
buildPhases = (
2FB345032DFBE270001A8A67 /* Sources */,
2FB345042DFBE270001A8A67 /* Frameworks */,
2FB345052DFBE270001A8A67 /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
2FB345092DFBE270001A8A67 /* EmotionMuseum */,
);
name = EmotionMuseum;
packageProductDependencies = (
);
productName = EmotionMuseum;
productReference = 2FB345072DFBE270001A8A67 /* EmotionMuseum.app */;
productType = "com.apple.product-type.application";
};
2FB345182DFBE273001A8A67 /* EmotionMuseumTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 2FB345302DFBE273001A8A67 /* Build configuration list for PBXNativeTarget "EmotionMuseumTests" */;
buildPhases = (
2FB345152DFBE273001A8A67 /* Sources */,
2FB345162DFBE273001A8A67 /* Frameworks */,
2FB345172DFBE273001A8A67 /* Resources */,
);
buildRules = (
);
dependencies = (
2FB3451B2DFBE273001A8A67 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
2FB3451C2DFBE273001A8A67 /* EmotionMuseumTests */,
);
name = EmotionMuseumTests;
packageProductDependencies = (
);
productName = EmotionMuseumTests;
productReference = 2FB345192DFBE273001A8A67 /* EmotionMuseumTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
2FB345222DFBE273001A8A67 /* EmotionMuseumUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 2FB345332DFBE273001A8A67 /* Build configuration list for PBXNativeTarget "EmotionMuseumUITests" */;
buildPhases = (
2FB3451F2DFBE273001A8A67 /* Sources */,
2FB345202DFBE273001A8A67 /* Frameworks */,
2FB345212DFBE273001A8A67 /* Resources */,
);
buildRules = (
);
dependencies = (
2FB345252DFBE273001A8A67 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
2FB345262DFBE273001A8A67 /* EmotionMuseumUITests */,
);
name = EmotionMuseumUITests;
packageProductDependencies = (
);
productName = EmotionMuseumUITests;
productReference = 2FB345232DFBE273001A8A67 /* EmotionMuseumUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
2FB344FF2DFBE270001A8A67 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1640;
LastUpgradeCheck = 1640;
TargetAttributes = {
2FB345062DFBE270001A8A67 = {
CreatedOnToolsVersion = 16.4;
};
2FB345182DFBE273001A8A67 = {
CreatedOnToolsVersion = 16.4;
TestTargetID = 2FB345062DFBE270001A8A67;
};
2FB345222DFBE273001A8A67 = {
CreatedOnToolsVersion = 16.4;
TestTargetID = 2FB345062DFBE270001A8A67;
};
};
};
buildConfigurationList = 2FB345022DFBE270001A8A67 /* Build configuration list for PBXProject "EmotionMuseum" */;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 2FB344FE2DFBE270001A8A67;
minimizedProjectReferenceProxies = 1;
preferredProjectObjectVersion = 77;
productRefGroup = 2FB345082DFBE270001A8A67 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
2FB345062DFBE270001A8A67 /* EmotionMuseum */,
2FB345182DFBE273001A8A67 /* EmotionMuseumTests */,
2FB345222DFBE273001A8A67 /* EmotionMuseumUITests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
2FB345052DFBE270001A8A67 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
2FB345172DFBE273001A8A67 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
2FB345212DFBE273001A8A67 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
2FB345032DFBE270001A8A67 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
2FB345152DFBE273001A8A67 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
2FB3451F2DFBE273001A8A67 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
2FB3451B2DFBE273001A8A67 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 2FB345062DFBE270001A8A67 /* EmotionMuseum */;
targetProxy = 2FB3451A2DFBE273001A8A67 /* PBXContainerItemProxy */;
};
2FB345252DFBE273001A8A67 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 2FB345062DFBE270001A8A67 /* EmotionMuseum */;
targetProxy = 2FB345242DFBE273001A8A67 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
2FB3452B2DFBE273001A8A67 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = JA6T4PANZM;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
2FB3452C2DFBE273001A8A67 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = JA6T4PANZM;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
2FB3452E2DFBE273001A8A67 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = JA6T4PANZM;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_LSApplicationQueriesSchemes = iosamap;
INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "需要使用您的位置信息来为您提供地图服务";
INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "需要使用您的位置信息来为您提供地图服务";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "需要使用您的位置信息来为您提供地图服务";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.dolphin.EmotionMuseum;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
2FB3452F2DFBE273001A8A67 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = JA6T4PANZM;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_LSApplicationQueriesSchemes = iosamap;
INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "需要使用您的位置信息来为您提供地图服务";
INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "需要使用您的位置信息来为您提供地图服务";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "需要使用您的位置信息来为您提供地图服务";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.dolphin.EmotionMuseum;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
2FB345312DFBE273001A8A67 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = JA6T4PANZM;
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = EmotionMuseumTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.dolphin.EmotionMuseumTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/EmotionMuseum.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/EmotionMuseum";
};
name = Debug;
};
2FB345322DFBE273001A8A67 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = JA6T4PANZM;
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = EmotionMuseumTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.dolphin.EmotionMuseumTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/EmotionMuseum.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/EmotionMuseum";
};
name = Release;
};
2FB345342DFBE273001A8A67 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = JA6T4PANZM;
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = EmotionMuseumUITests/Info.plist;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.dolphin.EmotionMuseumUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = EmotionMuseum;
};
name = Debug;
};
2FB345352DFBE273001A8A67 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = JA6T4PANZM;
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = EmotionMuseumUITests/Info.plist;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.dolphin.EmotionMuseumUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = EmotionMuseum;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
2FB345022DFBE270001A8A67 /* Build configuration list for PBXProject "EmotionMuseum" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2FB3452B2DFBE273001A8A67 /* Debug */,
2FB3452C2DFBE273001A8A67 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
2FB3452D2DFBE273001A8A67 /* Build configuration list for PBXNativeTarget "EmotionMuseum" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2FB3452E2DFBE273001A8A67 /* Debug */,
2FB3452F2DFBE273001A8A67 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
2FB345302DFBE273001A8A67 /* Build configuration list for PBXNativeTarget "EmotionMuseumTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2FB345312DFBE273001A8A67 /* Debug */,
2FB345322DFBE273001A8A67 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
2FB345332DFBE273001A8A67 /* Build configuration list for PBXNativeTarget "EmotionMuseumUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2FB345342DFBE273001A8A67 /* Debug */,
2FB345352DFBE273001A8A67 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 2FB344FF2DFBE270001A8A67 /* Project object */;
}
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildLocationStyle</key>
<string>UseAppPreferences</string>
<key>CustomBuildLocationType</key>
<string>RelativeToDerivedData</string>
<key>DerivedDataLocationStyle</key>
<string>Default</string>
<key>ShowSharedSchemesAutomaticallyEnabled</key>
<true/>
</dict>
</plist>
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>EmotionMuseum.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>
@@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "0.573",
"red" : "0.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "0.678",
"red" : "0.196"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,35 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.118",
"green" : "0.118",
"red" : "0.118"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.700",
"green" : "0.700",
"red" : "0.700"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.600",
"green" : "0.600",
"red" : "0.600"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.196",
"green" : "0.196",
"red" : "0.196"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,6 +0,0 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.788",
"green" : "0.780",
"red" : "0.776"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.329",
"green" : "0.310",
"red" : "0.298"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.300",
"green" : "0.200",
"red" : "0.900"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.400",
"green" : "0.300",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.929",
"green" : "0.569",
"red" : "0.416"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.929",
"green" : "0.569",
"red" : "0.416"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.000",
"red" : "0.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.980",
"green" : "0.780",
"red" : "0.310"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.780",
"green" : "0.580",
"red" : "0.210"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.600",
"green" : "0.600",
"red" : "0.600"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.780",
"green" : "0.780",
"red" : "0.780"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.925",
"green" : "0.925",
"red" : "0.925"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.294",
"green" : "0.294",
"red" : "0.294"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.980",
"green" : "0.980",
"red" : "0.980"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.392",
"green" : "0.392",
"red" : "0.392"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.380",
"green" : "0.780",
"red" : "0.200"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.480",
"green" : "0.880",
"red" : "0.300"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.961",
"green" : "0.961",
"red" : "0.961"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.173",
"green" : "0.169",
"red" : "0.165"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.700",
"green" : "0.700",
"red" : "0.700"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.600",
"green" : "0.600",
"red" : "0.600"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.200",
"green" : "0.700",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.300",
"green" : "0.800",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,75 +0,0 @@
//
// ContentView.swift
// EmotionMuseum
//
// Created by on 2025/6/13.
//
import SwiftUI
import CoreData
struct ContentView: View {
@EnvironmentObject var themeManager: ThemeManager
@EnvironmentObject var mockDataManager: MockDataManager
@EnvironmentObject var navigationManager: NavigationManager
@Environment(\.managedObjectContext) private var viewContext
var body: some View {
ZStack {
//
TabView(selection: $navigationManager.currentTab) {
RecordView()
.tabItem {
Image(systemName: "heart.text.square")
Text("记录")
}
.tag(MainTab.record)
GrowthView()
.tabItem {
Image(systemName: "leaf.arrow.circlepath")
Text("治愈")
}
.tag(MainTab.growth)
ExploreView()
.tabItem {
Image(systemName: "map")
Text("探索")
}
.tag(MainTab.explore)
UniverseView()
.tabItem {
Image(systemName: "person.circle")
Text("我的")
}
.tag(MainTab.insight)
}
.accentColor(Color("AccentColor"))
//
if navigationManager.isLoading {
LoadingOverlay(message: navigationManager.loadingMessage)
}
}
.preferredColorScheme(themeManager.isDarkMode ? .dark : .light)
.onAppear {
//
setupInitialState()
}
}
private func setupInitialState() {
//
navigationManager.currentTab = .record
}
}
#Preview {
ContentView()
.environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
.environmentObject(ThemeManager())
.environmentObject(MockDataManager.shared)
.environmentObject(NavigationManager())
}
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>EmotionMuseum.xcdatamodel</string>
</dict>
</plist>
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1" systemVersion="11A491" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="false" userDefinedModelVersionIdentifier="">
<entity name="Item" representedClassName="Item" syncable="YES" codeGenerationType="class">
<attribute name="timestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
</entity>
<elements>
<element name="Item" positionX="-63" positionY="-18" width="128" height="44"/>
</elements>
</model>
@@ -1,31 +0,0 @@
//
// EmotionMuseumApp.swift
// EmotionMuseum
//
// Created by on 2025/6/13.
//
import SwiftUI
@main
struct EmotionMuseumApp: App {
let persistenceController = PersistenceController.shared
@StateObject private var navigationManager = NavigationManager()
@StateObject private var themeManager = ThemeManager()
@StateObject private var mockDataManager = MockDataManager.shared
init() {
// SDK
MapManager.shared.configure()
}
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
.environmentObject(navigationManager)
.environmentObject(themeManager)
.environmentObject(mockDataManager)
}
}
}
@@ -1,189 +0,0 @@
//
// ChakraType.swift
// EmotionMuseum
//
// Created by on 2025/6/13.
//
import SwiftUI
enum ChakraType: String, CaseIterable {
case root = "海底轮"
case sacral = "脐轮"
case solarPlexus = "太阳轮"
case heart = "心轮"
case throat = "喉轮"
case thirdEye = "眉心轮"
case crown = "顶轮"
var color: Color {
switch self {
case .root:
return .red
case .sacral:
return .orange
case .solarPlexus:
return .yellow
case .heart:
return .green
case .throat:
return .blue
case .thirdEye:
return .indigo
case .crown:
return .purple
}
}
var position: CGPoint {
switch self {
case .root:
return CGPoint(x: 0.5, y: 0.9)
case .sacral:
return CGPoint(x: 0.5, y: 0.8)
case .solarPlexus:
return CGPoint(x: 0.5, y: 0.65)
case .heart:
return CGPoint(x: 0.5, y: 0.5)
case .throat:
return CGPoint(x: 0.5, y: 0.35)
case .thirdEye:
return CGPoint(x: 0.5, y: 0.2)
case .crown:
return CGPoint(x: 0.5, y: 0.05)
}
}
var description: String {
switch self {
case .root:
return "安全感、稳定性、生存本能"
case .sacral:
return "创造力、性能量、情感流动"
case .solarPlexus:
return "个人力量、自信、意志力"
case .heart:
return "爱、同情心、人际关系"
case .throat:
return "沟通、表达、真实性"
case .thirdEye:
return "直觉、洞察力、智慧"
case .crown:
return "灵性连接、觉知、超越"
}
}
var audioFileName: String {
switch self {
case .root:
return "root_chakra_healing"
case .sacral:
return "sacral_chakra_healing"
case .solarPlexus:
return "solar_plexus_chakra_healing"
case .heart:
return "heart_chakra_healing"
case .throat:
return "throat_chakra_healing"
case .thirdEye:
return "third_eye_chakra_healing"
case .crown:
return "crown_chakra_healing"
}
}
var frequency: String {
switch self {
case .root:
return "396 Hz"
case .sacral:
return "417 Hz"
case .solarPlexus:
return "528 Hz"
case .heart:
return "639 Hz"
case .throat:
return "741 Hz"
case .thirdEye:
return "852 Hz"
case .crown:
return "963 Hz"
}
}
var mantra: String {
switch self {
case .root:
return "LAM"
case .sacral:
return "VAM"
case .solarPlexus:
return "RAM"
case .heart:
return "YAM"
case .throat:
return "HAM"
case .thirdEye:
return "OM"
case .crown:
return "AH"
}
}
var element: String {
switch self {
case .root:
return ""
case .sacral:
return ""
case .solarPlexus:
return ""
case .heart:
return ""
case .throat:
return ""
case .thirdEye:
return ""
case .crown:
return "思想"
}
}
var keywords: [String] {
switch self {
case .root:
return ["安全感", "稳定", "生存", "根基", "物质"]
case .sacral:
return ["创造力", "性能量", "情感", "流动", "享受"]
case .solarPlexus:
return ["自信", "力量", "意志", "控制", "个性"]
case .heart:
return ["", "同情", "宽恕", "连接", "和谐"]
case .throat:
return ["表达", "沟通", "真实", "创意", "声音"]
case .thirdEye:
return ["直觉", "洞察", "智慧", "想象", "觉知"]
case .crown:
return ["灵性", "觉醒", "超越", "统一", "神圣"]
}
}
var healingBenefits: [String] {
switch self {
case .root:
return ["增强安全感", "改善焦虑", "提升专注力", "增强体力"]
case .sacral:
return ["激发创造力", "改善人际关系", "增强活力", "平衡情绪"]
case .solarPlexus:
return ["提升自信", "增强意志力", "改善消化", "释放压力"]
case .heart:
return ["开放心扉", "增强同理心", "改善关系", "释放怨恨"]
case .throat:
return ["提升表达能力", "增强创造力", "改善沟通", "释放恐惧"]
case .thirdEye:
return ["增强直觉", "提升洞察力", "改善专注", "开发智慧"]
case .crown:
return ["提升觉知", "增强灵性连接", "获得内在平静", "超越自我"]
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,23 +0,0 @@
import Foundation
// import AMapFoundationKit // CocoaPods
import CoreLocation
///
/// @Author huazhongmin
/// @Time 2024-03-24
/// @Description SDK
class MapManager {
static let shared = MapManager()
private init() {}
func configure() {
// TODO: CocoaPods
// AppKey
// AMapServices.shared().apiKey = "bb63ae64d651624f3673d61b47b45435"
//
let locationManager = CLLocationManager()
locationManager.requestWhenInUseAuthorization()
}
}
@@ -1,57 +0,0 @@
//
// Persistence.swift
// EmotionMuseum
//
// Created by on 2025/6/13.
//
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
@MainActor
static let preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
for _ in 0..<10 {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
}
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "EmotionMuseum")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
-11
View File
@@ -1,11 +0,0 @@
platform :ios, '14.0'
target 'EmotionMuseum' do
use_frameworks!
# 高德地图SDK
pod 'AMap3DMap'
pod 'AMapLocation'
pod 'AMapSearch'
end
@@ -1,549 +0,0 @@
//
// AIService.swift
// EmotionMuseum
//
// Created by on 2025/6/13.
//
import Foundation
import Combine
// MARK: - AI
protocol AIServiceProtocol {
func sendMessage(_ message: String, userId: UUID) async throws -> AIResponse
func analyzeEmotion(_ text: String) async throws -> EmotionAnalysis
func generateGrowthSuggestions(for stats: GrowthStats) async throws -> [GrowthTopic]
}
// MARK: - AI
struct AIResponse: Codable {
let messageId: UUID
let content: String
let emotionAnalysis: EmotionAnalysis
let suggestions: [String]
let followUpQuestions: [String]
let confidence: Float
let processingTime: TimeInterval
struct EmotionAnalysis: Codable {
let detectedEmotion: EmotionType
let intensity: Float
let triggers: [String]
let context: String
let recommendations: [String]
}
}
// MARK: - AI
class AIService: AIServiceProtocol, ObservableObject {
static let shared = AIService()
private let baseURL = "https://api.openai.com/v1"
private let apiKey: String
@Published var isLoading = false
@Published var lastError: Error?
private init() {
// API
self.apiKey = Bundle.main.infoDictionary?["OPENAI_API_KEY"] as? String ?? ""
}
// MARK: -
func sendMessage(_ message: String, userId: UUID) async throws -> AIResponse {
let startTime = Date()
isLoading = true
defer { isLoading = false }
let prompt = buildEmotionAnalysisPrompt(message: message)
let requestBody = OpenAIRequest(
model: "gpt-4",
messages: [
OpenAIMessage(role: "system", content: getSystemPrompt()),
OpenAIMessage(role: "user", content: prompt)
],
temperature: 0.7,
maxTokens: 500
)
do {
let response = try await sendOpenAIRequest(requestBody)
let processingTime = Date().timeIntervalSince(startTime)
return try parseAIResponse(response, messageId: UUID(), processingTime: processingTime)
} catch {
lastError = error
throw error
}
}
// MARK: -
func analyzeEmotion(_ text: String) async throws -> EmotionAnalysis {
let prompt = """
分析以下文本的情绪:"\(text)"
请返回JSON格式的分析结果,包含:
- summary: 简要总结
- keywords: 关键词数组
- suggestions: 建议数组
- moodPattern: 情绪模式
- confidence: 置信度(0-1)
"""
let requestBody = OpenAIRequest(
model: "gpt-4",
messages: [
OpenAIMessage(role: "system", content: "你是一个专业的情绪分析师,请用中文回答。"),
OpenAIMessage(role: "user", content: prompt)
],
temperature: 0.3,
maxTokens: 300
)
let response = try await sendOpenAIRequest(requestBody)
return try parseEmotionAnalysis(response)
}
// MARK: -
func generateGrowthSuggestions(for stats: GrowthStats) async throws -> [GrowthTopic] {
let prompt = """
基于用户的五维人格画像生成个性化成长建议:
- 自我感知: \(stats.selfAwareness)
- 情绪韧性: \(stats.emotionalResilience)
- 行动力: \(stats.actionPower)
- 共情力: \(stats.empathy)
- 生活热度: \(stats.lifeEnthusiasm)
请推荐3个最适合的成长课题,返回JSON格式。
"""
let requestBody = OpenAIRequest(
model: "gpt-4",
messages: [
OpenAIMessage(role: "system", content: "你是一个专业的心理成长顾问。"),
OpenAIMessage(role: "user", content: prompt)
],
temperature: 0.5,
maxTokens: 400
)
let response = try await sendOpenAIRequest(requestBody)
return try parseGrowthTopics(response)
}
// MARK: -
private func getSystemPrompt() -> String {
return """
你是情绪博物馆的AI情绪陪伴师,具有以下特质:
1. 专业且温暖:具备心理学知识,但表达温和亲切
2. 情绪敏感:能准确识别和回应用户的情绪状态
3. 个性化关怀:根据用户的话语提供针对性建议
4. 积极导向:引导用户朝着更健康的情绪状态发展
5. 边界清晰:不提供医疗建议,必要时建议寻求专业帮助
请用中文回答,保持对话自然流畅。
"""
}
private func buildEmotionAnalysisPrompt(message: String) -> String {
return """
用户消息:"\(message)"
请分析这条消息的情绪状态,并提供适当的回应。包含:
1. 检测到的主要情绪
2. 情绪强度(0-1)
3. 可能的触发因素
4. 温暖的回应内容
5. 具体的情绪调节建议
6. 后续探索问题
请以JSON格式返回分析结果。
"""
}
private func sendOpenAIRequest(_ request: OpenAIRequest) async throws -> OpenAIResponse {
guard !apiKey.isEmpty else {
throw AIServiceError.missingAPIKey
}
let url = URL(string: "\(baseURL)/chat/completions")!
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
let encoder = JSONEncoder()
urlRequest.httpBody = try encoder.encode(request)
let (data, response) = try await URLSession.shared.data(for: urlRequest)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw AIServiceError.apiError("请求失败")
}
let decoder = JSONDecoder()
return try decoder.decode(OpenAIResponse.self, from: data)
}
private func parseAIResponse(_ response: OpenAIResponse, messageId: UUID, processingTime: TimeInterval) throws -> AIResponse {
guard let choice = response.choices.first else {
throw AIServiceError.parseError("无法解析AI响应")
}
let content = choice.message.content
// JSON
if let jsonData = content.data(using: .utf8),
let parsed = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] {
let emotion = parseEmotionFromString(parsed["emotion"] as? String ?? "neutral")
let intensity = parsed["intensity"] as? Float ?? 0.5
let triggers = parsed["triggers"] as? [String] ?? []
let context = parsed["context"] as? String ?? ""
let recommendations = parsed["recommendations"] as? [String] ?? []
let suggestions = parsed["suggestions"] as? [String] ?? []
let followUpQuestions = parsed["followUpQuestions"] as? [String] ?? []
let responseContent = parsed["response"] as? String ?? content
let emotionAnalysis = AIResponse.EmotionAnalysis(
detectedEmotion: emotion,
intensity: intensity,
triggers: triggers,
context: context,
recommendations: recommendations
)
return AIResponse(
messageId: messageId,
content: responseContent,
emotionAnalysis: emotionAnalysis,
suggestions: suggestions,
followUpQuestions: followUpQuestions,
confidence: 0.8,
processingTime: processingTime
)
} else {
// JSON
return AIResponse(
messageId: messageId,
content: content,
emotionAnalysis: AIResponse.EmotionAnalysis(
detectedEmotion: .neutral,
intensity: 0.5,
triggers: [],
context: "基础对话",
recommendations: ["继续分享您的感受"]
),
suggestions: ["继续对话"],
followUpQuestions: ["还有什么想分享的吗?"],
confidence: 0.6,
processingTime: processingTime
)
}
}
private func parseEmotionAnalysis(_ response: OpenAIResponse) throws -> EmotionAnalysis {
guard response.choices.first != nil else {
throw AIServiceError.parseError("无法解析情绪分析响应")
}
// JSON
return EmotionAnalysis(
primaryEmotion: .neutral,
emotionIntensity: 0.5,
emotionTrend: .stable,
keywords: ["关键词1", "关键词2"],
aiInsights: "情绪分析摘要",
confidence: 0.75
)
}
private func parseGrowthTopics(_ response: OpenAIResponse) throws -> [GrowthTopic] {
// AI
return [
GrowthTopic(
title: "增强自我认知",
description: "通过反思练习提高自我觉察能力",
category: .selfAwareness,
difficulty: .beginner,
progress: 0.0
),
GrowthTopic(
title: "情绪调节技巧",
description: "学习有效的情绪管理方法",
category: .emotionRegulation,
difficulty: .intermediate,
progress: 0.0
),
GrowthTopic(
title: "提升沟通能力",
description: "改善人际关系和沟通技巧",
category: .relationships,
difficulty: .beginner,
progress: 0.0
)
]
}
private func parseEmotionFromString(_ emotionString: String) -> EmotionType {
switch emotionString.lowercased() {
case "joy", "happy", "开心", "喜悦": return .joy
case "sad", "sadness", "悲伤", "难过": return .sadness
case "angry", "anger", "愤怒", "生气": return .anger
case "fear", "scared", "恐惧", "害怕": return .fear
case "surprise", "surprised", "惊讶", "意外": return .surprise
default: return .neutral
}
}
}
// MARK: - OpenAI API
private struct OpenAIRequest: Codable {
let model: String
let messages: [OpenAIMessage]
let temperature: Float
let maxTokens: Int?
enum CodingKeys: String, CodingKey {
case model, messages, temperature
case maxTokens = "max_tokens"
}
}
private struct OpenAIMessage: Codable {
let role: String
let content: String
}
private struct OpenAIResponse: Codable {
let choices: [OpenAIChoice]
}
private struct OpenAIChoice: Codable {
let message: OpenAIMessage
}
// MARK: -
enum AIServiceError: LocalizedError {
case missingAPIKey
case apiError(String)
case parseError(String)
case networkError(Error)
var errorDescription: String? {
switch self {
case .missingAPIKey:
return "缺少API密钥"
case .apiError(let message):
return "API错误: \(message)"
case .parseError(let message):
return "解析错误: \(message)"
case .networkError(let error):
return "网络错误: \(error.localizedDescription)"
}
}
}
// MARK: - AI
class MockAIService: AIServiceProtocol, ObservableObject {
@Published var isLoading = false
func sendMessage(_ message: String, userId: UUID) async throws -> AIResponse {
isLoading = true
//
try await Task.sleep(nanoseconds: 1_000_000_000) // 1
isLoading = false
let emotion = analyzeEmotionFromMessage(message)
let response = generateMockResponse(for: emotion, message: message)
return AIResponse(
messageId: UUID(),
content: response,
emotionAnalysis: AIResponse.EmotionAnalysis(
detectedEmotion: emotion,
intensity: Float.random(in: 0.3...0.9),
triggers: extractTriggers(from: message),
context: "日常对话",
recommendations: generateRecommendations(for: emotion)
),
suggestions: generateSuggestions(for: emotion),
followUpQuestions: generateFollowUpQuestions(for: emotion),
confidence: Float.random(in: 0.6...0.9),
processingTime: 1.0
)
}
func analyzeEmotion(_ text: String) async throws -> EmotionAnalysis {
try await Task.sleep(nanoseconds: 500_000_000) // 0.5
return EmotionAnalysis(
primaryEmotion: analyzeEmotionFromMessage(text),
emotionIntensity: Float.random(in: 0.3...0.9),
emotionTrend: .stable,
keywords: ["情绪", "感受", "心情"],
aiInsights: "检测到\(analyzeEmotionFromMessage(text).rawValue)情绪",
confidence: Float.random(in: 0.7...0.95)
)
}
func generateGrowthSuggestions(for stats: GrowthStats) async throws -> [GrowthTopic] {
try await Task.sleep(nanoseconds: 800_000_000) // 0.8
return [
GrowthTopic(
title: "自我认知提升",
description: "通过日记和反思提高自我觉察",
category: .selfAwareness,
difficulty: .beginner,
progress: 0.0
),
GrowthTopic(
title: "情绪管理训练",
description: "学习情绪调节和压力缓解技巧",
category: .emotionRegulation,
difficulty: .intermediate,
progress: 0.0
)
]
}
// MARK: -
private func analyzeEmotionFromMessage(_ message: String) -> EmotionType {
let lowerMessage = message.lowercased()
if lowerMessage.contains("开心") || lowerMessage.contains("高兴") || lowerMessage.contains("快乐") {
return .joy
} else if lowerMessage.contains("难过") || lowerMessage.contains("悲伤") || lowerMessage.contains("伤心") {
return .sadness
} else if lowerMessage.contains("生气") || lowerMessage.contains("愤怒") || lowerMessage.contains("烦躁") {
return .anger
} else if lowerMessage.contains("害怕") || lowerMessage.contains("恐惧") || lowerMessage.contains("紧张") {
return .fear
} else if lowerMessage.contains("惊讶") || lowerMessage.contains("意外") {
return .surprise
} else {
return .neutral
}
}
private func generateMockResponse(for emotion: EmotionType, message: String) -> String {
switch emotion {
case .joy:
return "我能感受到你的开心!这种积极的情绪很珍贵,记得把这份快乐分享给身边的人。是什么让你感到如此愉悦呢?"
case .sadness:
return "我理解你现在的感受,难过是正常的情绪反应。允许自己感受这些情绪,同时也要温柔地照顾自己。你愿意分享更多吗?"
case .anger:
return "我能察觉到你的愤怒情绪。愤怒往往是其他情绪的表达,比如受伤或失望。深呼吸一下,我们一起探索这种感受的根源。"
case .fear:
return "恐惧感让人不安,这是很自然的反应。记住,你有能力面对困难。让我们一起分析一下你担心的事情,也许并不像想象中那么可怕。"
case .surprise:
return "意外的事情总是让人印象深刻!无论是好的惊喜还是让人措手不及的情况,都是生活的一部分。告诉我更多细节吧。"
case .neutral:
return "我在聆听你的分享。有时候平静也是一种很好的状态。如果你想深入探讨任何感受或想法,我都愿意陪伴你。"
case .anxiety:
return "我能感受到你的焦虑情绪。焦虑是很常见的感受,让我们一起找到缓解的方法。"
case .excitement:
return "你的兴奋情绪很有感染力!这种充满活力的状态真的很棒。告诉我是什么让你如此激动,我也想分享你的喜悦!"
case .contentment:
return "你的满足感让我很欣慰。这种内心的平和与充实是生活中最珍贵的状态之一。珍惜这份宁静,它会给你力量。"
case .confusion:
return "我感受到你内心的困惑。困惑是思考和成长的开始,让我们一起理清思路。"
case .melancholy:
return "我感受到你内心的忧郁。这种淡淡的愁绪有时候也是一种美,它让我们更深刻地感受生活。愿意和我分享你的思绪吗?"
}
}
private func extractTriggers(from message: String) -> [String] {
//
let keywords = ["工作", "家庭", "朋友", "健康", "学习", "感情", "压力", "变化"]
return keywords.filter { message.contains($0) }
}
private func generateRecommendations(for emotion: EmotionType) -> [String] {
switch emotion {
case .joy:
return ["记录这个美好时刻", "分享你的快乐", "感恩当下"]
case .sadness:
return ["允许自己哭泣", "寻求朋友支持", "进行轻柔运动"]
case .anger:
return ["深呼吸放松", "写下愤怒的原因", "进行体力活动"]
case .fear:
return ["面对恐惧", "寻求专业建议", "制定应对计划"]
case .surprise:
return ["接受变化", "保持开放心态", "记录感受"]
case .neutral:
return ["享受平静", "进行自我反思", "设定新目标"]
case .anxiety:
return ["深呼吸练习", "寻求支持", "制定应对计划"]
case .excitement:
return ["合理安排时间", "保持专注", "与他人分享喜悦"]
case .contentment:
return ["珍惜当下", "保持感恩心", "分享你的平和"]
case .confusion:
return ["整理思路", "寻求建议", "分步骤思考"]
case .melancholy:
return ["接受这种情绪", "寻找美好的事物", "与朋友交流"]
}
}
private func generateSuggestions(for emotion: EmotionType) -> [String] {
switch emotion {
case .joy:
return ["继续保持积极心态", "做些让你快乐的事"]
case .sadness:
return ["给自己一些时间", "考虑专业帮助"]
case .anger:
return ["找到健康的发泄方式", "思考问题的解决方案"]
case .fear:
return ["一步步面对恐惧", "建立支持系统"]
case .surprise:
return ["适应新情况", "保持灵活性"]
case .neutral:
return ["探索新的兴趣", "建立日常习惯"]
case .anxiety:
return ["学习放松技巧", "寻求专业帮助"]
case .excitement:
return ["制定行动计划", "保持理性思考"]
case .contentment:
return ["维持内心平衡", "继续当前的生活方式"]
case .confusion:
return ["寻找清晰的方向", "与他人交流想法"]
case .melancholy:
return ["接受当下的感受", "寻找内心的平静"]
}
}
private func generateFollowUpQuestions(for emotion: EmotionType) -> [String] {
switch emotion {
case .joy:
return ["是什么特别的事情让你如此开心?", "你想如何延续这种快乐?"]
case .sadness:
return ["这种感受持续多久了?", "有什么可以帮助你感觉好一些?"]
case .anger:
return ["什么事情触发了这种愤怒?", "你通常如何处理愤怒情绪?"]
case .fear:
return ["你最担心的是什么?", "有什么可以让你感到更安全?"]
case .surprise:
return ["这个意外对你意味着什么?", "你如何适应这个变化?"]
case .neutral:
return ["最近有什么新的想法吗?", "有什么目标想要实现?"]
case .anxiety:
return ["这种焦虑从什么时候开始的?", "有什么特别担心的事情吗?"]
case .excitement:
return ["是什么让你如此兴奋?", "你计划如何行动?"]
case .contentment:
return ["是什么让你感到如此满足?", "这种状态对你意味着什么?"]
case .confusion:
return ["什么让你感到困惑?", "需要帮助理清哪些思路?"]
case .melancholy:
return ["这种忧郁感从何而来?", "有什么特别的回忆或想法吗?"]
}
}
}
@@ -1,701 +0,0 @@
//
// MockDataManager.swift
// EmotionMuseum
//
// Created by on 2025/7/5.
//
import Foundation
import SwiftUI
class MockDataManager: ObservableObject {
static let shared = MockDataManager()
// MARK: - Published Properties
@Published var currentUser: User
@Published var conversations: [Conversation] = []
@Published var growthTopics: [GrowthTopic] = []
@Published var locationPins: [LocationPin] = []
@Published var communityPosts: [CommunityPost] = []
@Published var emotionRecords: [EmotionRecord] = []
@Published var achievements: [Achievement] = []
@Published var userStats: UserStats = UserStats()
@Published var weeklyStats: WeeklyStats
private init() {
//
self.currentUser = User(
username: "emotion_explorer",
email: "user@example.com",
profile: UserProfile(
nickname: "情绪探索者",
birthDate: Calendar.current.date(byAdding: .year, value: -25, to: Date()),
location: "北京市",
bio: "在情绪的海洋中寻找内心的平静",
memberLevel: .premium,
totalDays: 127,
growthStats: GrowthStats(
selfAwareness: 78.5,
emotionalResilience: 65.2,
actionPower: 72.8,
empathy: 85.3,
lifeEnthusiasm: 69.7
)
),
createdAt: Calendar.current.date(byAdding: .day, value: -127, to: Date()) ?? Date()
)
//
let weekStart = Calendar.current.dateInterval(of: .weekOfYear, for: Date())?.start ?? Date()
self.weeklyStats = WeeklyStats(weekStartDate: weekStart)
//
generateAllMockData()
}
// MARK: - Public Methods
func generateAllMockData() {
generateMockEmotionRecords()
generateMockConversations()
generateMockGrowthTopics()
generateMockLocationPins()
generateMockCommunityPosts()
generateMockAchievements()
updateUserStats()
updateWeeklyStats()
}
func refreshData() async {
await MainActor.run {
generateAllMockData()
}
}
// MARK: - Conversation Methods
func addMessage(to conversationId: UUID, content: String, sender: MessageSender) {
if let index = conversations.firstIndex(where: { $0.id == conversationId }) {
let message = Message(
conversationId: conversationId,
content: content,
type: .text,
sender: sender,
emotionScore: sender == .user ? Float.random(in: 0.3...0.9) : nil
)
conversations[index].messages.append(message)
if sender == .user {
// AI
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
let aiResponse = self.generateAIResponse(for: content)
self.addMessage(to: conversationId, content: aiResponse, sender: .ai)
}
}
}
}
func createNewConversation() -> Conversation {
let conversation = Conversation(
userId: currentUser.id,
title: "新对话 \(Date().shortFormat)"
)
conversations.insert(conversation, at: 0)
return conversation
}
// MARK: - Growth Topic Methods
func updateTopicProgress(_ topicId: UUID, progress: Float) {
if let index = growthTopics.firstIndex(where: { $0.id == topicId }) {
growthTopics[index].progress = min(1.0, progress)
if growthTopics[index].progress >= 1.0 {
growthTopics[index].completedAt = Date()
//
let reward = Reward(
type: .points,
title: "课题完成",
description: "完成了\(growthTopics[index].title)",
value: 100,
rarity: .common
)
growthTopics[index].rewards.append(reward)
}
}
}
func addTopicInteraction(_ topicId: UUID, type: InteractionType, title: String, content: String) {
if let index = growthTopics.firstIndex(where: { $0.id == topicId }) {
let interaction = TopicInteraction(
topicId: topicId,
type: type,
title: title,
content: content,
completedAt: Date(),
duration: TimeInterval.random(in: 300...1800)
)
growthTopics[index].interactions.append(interaction)
//
let progressIncrease: Float = 0.2
updateTopicProgress(topicId, progress: growthTopics[index].progress + progressIncrease)
}
}
// MARK: - Location Methods
func toggleLocationBookmark(_ locationId: UUID) {
if let index = locationPins.firstIndex(where: { $0.id == locationId }) {
locationPins[index].isBookmarked.toggle()
}
}
func addLocationVisit(_ locationId: UUID) {
if let index = locationPins.firstIndex(where: { $0.id == locationId }) {
locationPins[index].visits += 1
}
}
// MARK: - Community Methods
func togglePostLike(_ postId: UUID) {
if let index = communityPosts.firstIndex(where: { $0.id == postId }) {
communityPosts[index].isLikedByCurrentUser.toggle()
if communityPosts[index].isLikedByCurrentUser {
communityPosts[index].likes += 1
} else {
communityPosts[index].likes = max(0, communityPosts[index].likes - 1)
}
}
}
func addComment(to postId: UUID, content: String) {
if let index = communityPosts.firstIndex(where: { $0.id == postId }) {
let comment = Comment(
postId: postId,
userId: currentUser.id,
content: content
)
communityPosts[index].comments.append(comment)
}
}
// MARK: - Private Data Generation Methods
private func generateMockEmotionRecords() {
emotionRecords.removeAll()
// 30
for i in 0..<30 {
let date = Calendar.current.date(byAdding: .day, value: -i, to: Date()) ?? Date()
let recordCount = Int.random(in: 1...3) // 1-3
for _ in 0..<recordCount {
let record = EmotionRecord(
userId: currentUser.id,
date: date,
emotionType: EmotionType.allCases.randomElement() ?? .neutral,
intensity: Float.random(in: 0.2...0.9),
context: generateEmotionContext(),
triggers: generateEmotionTriggers(),
location: ["家里", "公司", "咖啡厅", "公园", "地铁上"].randomElement(),
weather: ["晴天", "阴天", "雨天", "雪天"].randomElement(),
notes: generateEmotionNotes()
)
emotionRecords.append(record)
}
}
emotionRecords.sort { $0.date > $1.date }
}
private func generateMockConversations() {
conversations.removeAll()
// 30
for i in 0..<15 {
let startDate = Calendar.current.date(byAdding: .day, value: -i*2, to: Date()) ?? Date()
let conversation = createMockConversation(for: startDate, index: i)
conversations.append(conversation)
}
conversations.sort { $0.startTime > $1.startTime }
}
private func generateMockGrowthTopics() {
growthTopics.removeAll()
// 3-4
for category in TopicCategory.allCases {
for i in 1...4 {
let topic = createMockTopic(category: category, index: i)
growthTopics.append(topic)
}
}
}
private func generateMockLocationPins() {
locationPins.removeAll()
//
let beijingLocations = [
(39.9042, 116.4074, "天安门广场", "北京的心脏,见证历史的地方"),
(39.9163, 116.3972, "故宫博物院", "明清两代的皇家宫殿,文化瑰宝"),
(40.0031, 116.3272, "颐和园", "清代皇家园林,山水如画"),
(39.8844, 116.5564, "798艺术区", "现代艺术的聚集地,创意无限"),
(39.9389, 116.3467, "什刹海", "老北京的韵味,夜晚格外美丽"),
(39.9056, 116.3913, "南锣鼓巷", "胡同文化的代表,小资情调"),
(40.0090, 116.2755, "香山公园", "秋天赏红叶的绝佳去处"),
(39.8838, 116.4649, "朝阳公园", "都市中的绿洲,休闲好去处"),
(39.9280, 116.3835, "后海", "酒吧一条街,夜生活的天堂"),
(39.9170, 116.3970, "景山公园", "俯瞰紫禁城的最佳位置")
]
for (_, location) in beijingLocations.enumerated() {
let pin = LocationPin(
coordinate: Coordinate(latitude: location.0, longitude: location.1),
title: location.2,
description: location.3,
type: LocationType.allCases.randomElement() ?? .community,
emotionTags: Array(EmotionType.allCases.shuffled().prefix(Int.random(in: 1...3))),
photos: generateLocationPhotos(),
createdBy: Bool.random() ? currentUser.id : UUID(),
createdAt: Calendar.current.date(byAdding: .day, value: -Int.random(in: 1...90), to: Date()) ?? Date(),
likes: Int.random(in: 5...200),
visits: Int.random(in: 10...500),
address: "北京市" + ["东城区", "西城区", "朝阳区", "海淀区"].randomElement()!,
category: LocationCategory.allCases.randomElement() ?? .other,
isBookmarked: Bool.random()
)
locationPins.append(pin)
}
}
private func generateMockCommunityPosts() {
communityPosts.removeAll()
for _ in 0..<20 {
let post = CommunityPost(
userId: Bool.random() ? currentUser.id : UUID(),
locationId: locationPins.randomElement()?.id,
content: generatePostContent(),
photos: generatePostPhotos(),
tags: generatePostTags(),
likes: Int.random(in: 0...100),
comments: generatePostComments(),
createdAt: Calendar.current.date(byAdding: .hour, value: -Int.random(in: 1...168), to: Date()) ?? Date(),
isPrivate: Bool.random(),
viewCount: Int.random(in: 10...1000),
type: PostType.allCases.randomElement() ?? .general,
isLikedByCurrentUser: Bool.random()
)
communityPosts.append(post)
}
communityPosts.sort { $0.createdAt > $1.createdAt }
}
private func generateMockAchievements() {
achievements.removeAll()
let achievementData: [(String, String, AchievementCategory, String, RewardRarity, AchievementRequirement, Int, Bool)] = [
("初次对话", "完成第一次AI对话", .conversation, "message.circle", .common, .conversationCount(1), 1, false),
("话痨达人", "累计对话100次", .conversation, "message.badge", .rare, .conversationCount(100), 45, false),
("情绪记录者", "记录50次情绪", .emotion, "heart.circle", .common, .emotionRecordCount(50), 32, false),
("成长新手", "完成第一个成长课题", .growth, "arrow.up.circle", .common, .topicCompletion(1), 1, false),
("社交达人", "获得100个点赞", .social, "heart.badge", .rare, .socialInteraction(100), 67, false),
("探索者", "访问10个地点", .exploration, "map.circle", .common, .locationVisit(10), 8, false),
("坚持不懈", "连续使用30天", .consistency, "calendar.badge", .epic, .consecutiveDays(30), 25, false),
("积分大户", "累计获得1000积分", .milestone, "star.badge", .rare, .totalPoints(1000), 750, false),
("神秘成就", "发现隐藏彩蛋", .special, "sparkles", .legendary, .special("发现应用中的隐藏彩蛋"), 0, true)
]
for data in achievementData {
let achievement = Achievement(
title: data.0,
description: data.1,
category: data.2,
icon: data.3,
rarity: data.4,
requirement: data.5,
progress: data.6,
targetValue: getTargetValue(for: data.5),
unlockedAt: data.6 >= getTargetValue(for: data.5) ? Date() : nil,
isHidden: data.7
)
achievements.append(achievement)
}
}
// MARK: - Helper Methods
private func createMockConversation(for date: Date, index: Int) -> Conversation {
let conversation = Conversation(
userId: currentUser.id,
title: generateConversationTitle(index: index),
startTime: date,
endTime: Calendar.current.date(byAdding: .minute, value: Int.random(in: 5...60), to: date),
tags: generateConversationTags()
)
//
var messages: [Message] = []
let messageCount = Int.random(in: 4...12)
for i in 0..<messageCount {
let isUserMessage = i % 2 == 0
let messageTime = Calendar.current.date(byAdding: .minute, value: i * 2, to: date) ?? date
let message = Message(
conversationId: conversation.id,
content: isUserMessage ? generateUserMessage() : generateAIMessage(),
type: .text,
sender: isUserMessage ? .user : .ai,
timestamp: messageTime,
emotionScore: isUserMessage ? Float.random(in: 0.2...0.9) : nil
)
messages.append(message)
}
var updatedConversation = conversation
updatedConversation.messages = messages
//
if !messages.isEmpty {
updatedConversation.emotionAnalysis = EmotionAnalysis(
primaryEmotion: EmotionType.allCases.randomElement() ?? .neutral,
emotionIntensity: Float.random(in: 0.3...0.8),
emotionTrend: EmotionTrend.allCases.randomElement() ?? .stable,
keywords: generateEmotionKeywords(),
aiInsights: generateAIInsights()
)
}
return updatedConversation
}
private func createMockTopic(category: TopicCategory, index: Int) -> GrowthTopic {
let topicTitles = getTopicTitles(for: category)
let title = topicTitles[min(index - 1, topicTitles.count - 1)]
return GrowthTopic(
title: title,
description: generateTopicDescription(for: title),
category: category,
difficulty: Difficulty.allCases.randomElement() ?? .beginner,
progress: Float.random(in: 0...1),
level: Int.random(in: 1...5),
totalLevels: 5,
isUnlocked: index <= 2 || Bool.random(), //
completedAt: Float.random(in: 0...1) > 0.7 ? Date() : nil,
rewards: generateTopicRewards(),
interactions: generateTopicInteractions(),
estimatedDuration: TimeInterval.random(in: 1800...7200), // 302
prerequisites: index > 1 ? [UUID()] : []
)
}
private func generateAIResponse(for userMessage: String) -> String {
let responses = [
"我理解你的感受,这确实是一个值得思考的问题。",
"你提到的这个情况很常见,让我们一起来分析一下。",
"从你的描述中,我感受到了你的情绪变化。",
"这是一个很好的观察,你有什么想法吗?",
"我听到了你的担忧,我们可以一步步来解决。",
"你的感受是完全可以理解的,很多人都会有类似的经历。",
"让我们换个角度来看这个问题,可能会有新的发现。",
"你已经很勇敢地表达了自己的想法,这很棒。",
"我注意到你提到了一些关键词,我们可以深入探讨一下。",
"你的情绪管理能力在不断提升,继续保持。"
]
return responses.randomElement() ?? "谢谢你的分享。"
}
// MARK: - Content Generation Methods
private func generateEmotionContext() -> String {
let contexts = [
"工作压力让我感到疲惫",
"和朋友聊天后心情变好了",
"看到美丽的日落感到平静",
"遇到挫折时感到沮丧",
"完成任务后有成就感",
"听音乐时情绪放松",
"运动后感到充满活力",
"独处时思考人生",
"与家人团聚很温暖",
"面对未知感到紧张"
]
return contexts.randomElement() ?? "日常生活中的情绪体验"
}
private func generateEmotionTriggers() -> [String] {
let allTriggers = ["工作", "人际关系", "健康", "家庭", "学习", "金钱", "未来", "过去", "天气", "音乐"]
return Array(allTriggers.shuffled().prefix(Int.random(in: 1...3)))
}
private func generateEmotionNotes() -> String? {
let notes = [
"今天的情绪比昨天好一些",
"需要更多的休息时间",
"和朋友的谈话很有帮助",
"运动确实能改善心情",
"要学会接受自己的情绪",
nil, nil //
]
return notes.randomElement() ?? nil
}
private func generateConversationTitle(index: Int) -> String {
let titles = [
"今天的心情分享",
"关于压力管理的讨论",
"人际关系的困惑",
"职场焦虑的缓解",
"自我成长的反思",
"情绪调节的方法",
"生活目标的规划",
"内心平静的追求",
"人生意义的探索",
"幸福感的提升"
]
return titles[index % titles.count]
}
private func generateConversationTags() -> [String] {
let allTags = ["情绪管理", "压力缓解", "人际关系", "自我成长", "生活规划", "心理健康"]
return Array(allTags.shuffled().prefix(Int.random(in: 1...3)))
}
private func generateUserMessage() -> String {
let messages = [
"我最近感到有些焦虑,不知道该怎么办。",
"工作压力很大,总是担心做不好。",
"和朋友的关系出现了一些问题。",
"我想要改变现在的生活状态。",
"有时候感到很孤独,需要有人倾听。",
"对未来感到不确定,有些迷茫。",
"今天心情不错,想分享一下。",
"我在思考人生的意义是什么。",
"想要培养一些新的习惯。",
"感觉自己需要更多的自信。"
]
return messages.randomElement() ?? "你好"
}
private func generateAIMessage() -> String {
let messages = [
"我理解你的感受,焦虑是很正常的情绪反应。",
"工作压力确实会影响我们的心情,不妨试试放松技巧。",
"人际关系需要时间和耐心来维护,你做得很好。",
"改变需要勇气,你已经迈出了第一步。",
"孤独感是人类共同的体验,你并不孤单。",
"对未来的不确定感是成长的一部分。",
"很高兴听到你今天心情不错!",
"人生意义的探索是一个持续的过程。",
"培养新习惯需要时间,要对自己有耐心。",
"自信是可以通过练习来培养的。"
]
return messages.randomElement() ?? "谢谢你的分享"
}
private func generateEmotionKeywords() -> [String] {
let keywords = ["压力", "焦虑", "快乐", "悲伤", "希望", "困惑", "平静", "兴奋", "担忧", "满足"]
return Array(keywords.shuffled().prefix(Int.random(in: 2...4)))
}
private func generateAIInsights() -> String {
let insights = [
"你的情绪表达能力在不断提升,这是很好的进步。",
"从对话中可以看出你对自我成长很有意识。",
"你善于反思,这有助于情绪的自我调节。",
"你的积极态度值得赞赏,继续保持。",
"建议多关注自己的情绪变化模式。",
"你的表达很真诚,这有助于深入的自我探索。"
]
return insights.randomElement() ?? "继续保持这种开放的态度"
}
private func getTopicTitles(for category: TopicCategory) -> [String] {
switch category {
case .selfAwareness:
return ["认识真实的自己", "探索内在价值观", "发现个人优势", "理解情绪模式"]
case .emotionRegulation:
return ["压力管理技巧", "愤怒情绪调节", "焦虑缓解方法", "悲伤情绪处理"]
case .socialSkills:
return ["有效沟通技巧", "建立良好关系", "冲突解决能力", "团队合作精神"]
case .stressManagement:
return ["工作压力应对", "时间管理技能", "放松训练方法", "心理韧性建设"]
case .lifeGoals:
return ["目标设定方法", "人生规划技巧", "价值观澄清", "意义感培养"]
case .mindfulness:
return ["正念冥想入门", "专注力训练", "当下觉察练习", "内心平静修炼"]
case .relationships:
return ["亲密关系维护", "友谊经营之道", "家庭和谐相处", "社交边界设定"]
case .creativity:
return ["创意思维开发", "艺术表达练习", "问题解决创新", "想象力激发"]
}
}
private func generateTopicDescription(for title: String) -> String {
return "通过系统化的学习和练习,帮助你在\(title)方面获得提升。包含理论知识、实践练习和个人反思,让你在成长的道路上更进一步。"
}
private func generateTopicRewards() -> [Reward] {
let rewardCount = Int.random(in: 0...2)
var rewards: [Reward] = []
for _ in 0..<rewardCount {
let reward = Reward(
type: RewardType.allCases.randomElement() ?? .points,
title: "课题奖励",
description: "完成课题获得的奖励",
value: Int.random(in: 50...200),
rarity: RewardRarity.allCases.randomElement() ?? .common
)
rewards.append(reward)
}
return rewards
}
private func generateTopicInteractions() -> [TopicInteraction] {
let interactionCount = Int.random(in: 0...3)
var interactions: [TopicInteraction] = []
for i in 0..<interactionCount {
let interaction = TopicInteraction(
topicId: UUID(),
type: InteractionType.allCases.randomElement() ?? .aiChat,
title: "互动\(i+1)",
content: "这是一个有意义的互动内容",
completedAt: Bool.random() ? Date() : nil,
duration: TimeInterval.random(in: 300...1800),
rating: Bool.random() ? Int.random(in: 3...5) : nil
)
interactions.append(interaction)
}
return interactions
}
private func generateLocationPhotos() -> [String] {
let photoCount = Int.random(in: 1...4)
return Array(repeating: "location_photo", count: photoCount)
}
private func generatePostContent() -> String {
let contents = [
"今天在这个美丽的地方找到了内心的平静 ✨",
"和朋友一起度过了愉快的下午时光 😊",
"这里的风景让我想起了童年的回忆",
"在这个安静的角落里思考人生的意义",
"发现了一个治愈心灵的好地方",
"阳光透过树叶洒下来,心情瞬间明亮了",
"和陌生人的一次偶遇,让我对生活有了新的感悟",
"在这里感受到了城市中难得的宁静",
"每次来这里都能获得新的启发",
"分享一个让我感到温暖的瞬间"
]
return contents.randomElement() ?? "分享今天的美好时光"
}
private func generatePostPhotos() -> [String] {
let photoCount = Int.random(in: 0...3)
return Array(repeating: "post_photo", count: photoCount)
}
private func generatePostTags() -> [String] {
let allTags = ["治愈", "美好", "分享", "生活", "感悟", "风景", "友谊", "成长", "平静", "温暖"]
return Array(allTags.shuffled().prefix(Int.random(in: 1...3)))
}
private func generatePostComments() -> [Comment] {
let commentCount = Int.random(in: 0...5)
var comments: [Comment] = []
let commentContents = [
"太美了!",
"我也想去这个地方",
"感谢分享 ❤️",
"很有感触",
"下次一起去吧",
"照片拍得很棒",
"这个地方我也去过",
"很治愈的分享"
]
for _ in 0..<commentCount {
let comment = Comment(
postId: UUID(),
userId: UUID(),
content: commentContents.randomElement() ?? "很棒的分享",
createdAt: Calendar.current.date(byAdding: .hour, value: -Int.random(in: 1...24), to: Date()) ?? Date(),
likes: Int.random(in: 0...10),
isLikedByCurrentUser: Bool.random()
)
comments.append(comment)
}
return comments
}
private func getTargetValue(for requirement: AchievementRequirement) -> Int {
switch requirement {
case .conversationCount(let count): return count
case .emotionRecordCount(let count): return count
case .topicCompletion(let count): return count
case .socialInteraction(let count): return count
case .locationVisit(let count): return count
case .consecutiveDays(let days): return days
case .totalPoints(let points): return points
case .special(_): return 1
}
}
private func updateUserStats() {
userStats.totalConversations = conversations.count
userStats.totalMessages = conversations.reduce(0) { $0 + $1.messageCount }
userStats.totalEmotionRecords = emotionRecords.count
userStats.completedTopics = growthTopics.filter { $0.isCompleted }.count
userStats.totalPoints = achievements.reduce(0) { $0 + ($1.isUnlocked ? 100 : 0) }
userStats.consecutiveDays = 25 // 使
userStats.maxConsecutiveDays = 30
userStats.socialInteractions = communityPosts.reduce(0) { $0 + $1.likes + $1.commentCount }
userStats.locationsVisited = locationPins.filter { $0.visits > 0 }.count
userStats.postsCreated = communityPosts.filter { $0.userId == currentUser.id }.count
userStats.likesReceived = communityPosts.filter { $0.userId == currentUser.id }.reduce(0) { $0 + $1.likes }
userStats.commentsReceived = communityPosts.filter { $0.userId == currentUser.id }.reduce(0) { $0 + $1.commentCount }
}
private func updateWeeklyStats() {
let weekStart = weeklyStats.weekStartDate
let weekEnd = Calendar.current.date(byAdding: .day, value: 6, to: weekStart) ?? weekStart
//
weeklyStats.emotionRecords = emotionRecords.filter { record in
record.date >= weekStart && record.date <= weekEnd
}
//
weeklyStats.conversations = conversations.filter { conversation in
conversation.startTime >= weekStart && conversation.startTime <= weekEnd
}
//
if !weeklyStats.emotionRecords.isEmpty {
weeklyStats.averageMoodScore = weeklyStats.emotionRecords.averageIntensity()
weeklyStats.dominantEmotion = weeklyStats.emotionRecords.dominantEmotion()
}
//
weeklyStats.moodTrend = EmotionTrend.allCases.randomElement() ?? .stable
//
let dailyActivity = Dictionary(grouping: weeklyStats.emotionRecords + weeklyStats.conversations.map { conversation in
EmotionRecord(userId: currentUser.id, date: conversation.startTime, emotionType: .neutral, intensity: 0.5, context: "对话活动")
}, by: { Calendar.current.startOfDay(for: $0.date) })
weeklyStats.mostActiveDay = dailyActivity.max(by: { $0.value.count < $1.value.count })?.key
}
}
@@ -1,411 +0,0 @@
//
// NavigationManager.swift
// EmotionMuseum
//
// Created by on 2025/7/5.
//
import Foundation
import SwiftUI
class NavigationManager: ObservableObject {
// MARK: - Tab Navigation
@Published var currentTab: MainTab = .record
// MARK: - Navigation Paths for each tab
@Published var recordNavigation = NavigationPath()
@Published var growthNavigation = NavigationPath()
@Published var exploreNavigation = NavigationPath()
@Published var insightNavigation = NavigationPath()
// MARK: - Global Modal States
@Published var showingChatHistory = false
@Published var showingFullScreenChat = false
@Published var showingSettings = false
@Published var showingProfile = false
@Published var showingThemeSettings = false
@Published var showingAddLocation = false
@Published var showingTopicDetail = false
@Published var showingLocationDetail = false
@Published var showingPostDetail = false
@Published var showingAchievements = false
@Published var showingMemberCenter = false
// MARK: - Current Selection States
@Published var selectedConversation: Conversation?
@Published var selectedTopic: GrowthTopic?
@Published var selectedLocation: LocationPin?
@Published var selectedPost: CommunityPost?
@Published var selectedAchievement: Achievement?
// MARK: - Chat States
@Published var currentChatConversation: Conversation?
@Published var isVoiceMode = false
@Published var chatInputText = ""
// MARK: - Loading States
@Published var isLoading = false
@Published var loadingMessage = ""
// MARK: - Navigation Methods
func navigateToTab(_ tab: MainTab) {
withAnimation(.easeInOut(duration: 0.3)) {
currentTab = tab
}
}
func navigateToChat(conversation: Conversation? = nil) {
selectedConversation = conversation
currentChatConversation = conversation
showingFullScreenChat = true
}
func navigateToTopic(_ topic: GrowthTopic) {
selectedTopic = topic
showingTopicDetail = true
}
func navigateToLocation(_ location: LocationPin) {
selectedLocation = location
showingLocationDetail = true
}
func navigateToPost(_ post: CommunityPost) {
selectedPost = post
showingPostDetail = true
}
func navigateToProfile() {
showingProfile = true
}
func navigateToSettings() {
showingSettings = true
}
func navigateToThemeSettings() {
showingThemeSettings = true
}
func navigateToAchievements() {
showingAchievements = true
}
func navigateToMemberCenter() {
showingMemberCenter = true
}
func showAddLocation() {
showingAddLocation = true
}
func showChatHistory() {
showingChatHistory = true
}
// MARK: - Dismiss Methods
func dismissAllModals() {
showingChatHistory = false
showingFullScreenChat = false
showingSettings = false
showingProfile = false
showingThemeSettings = false
showingAddLocation = false
showingTopicDetail = false
showingLocationDetail = false
showingPostDetail = false
showingAchievements = false
showingMemberCenter = false
selectedConversation = nil
selectedTopic = nil
selectedLocation = nil
selectedPost = nil
selectedAchievement = nil
currentChatConversation = nil
}
func dismissCurrentModal() {
if showingFullScreenChat {
showingFullScreenChat = false
currentChatConversation = nil
} else if showingChatHistory {
showingChatHistory = false
} else if showingSettings {
showingSettings = false
} else if showingProfile {
showingProfile = false
} else if showingThemeSettings {
showingThemeSettings = false
} else if showingAddLocation {
showingAddLocation = false
} else if showingTopicDetail {
showingTopicDetail = false
selectedTopic = nil
} else if showingLocationDetail {
showingLocationDetail = false
selectedLocation = nil
} else if showingPostDetail {
showingPostDetail = false
selectedPost = nil
} else if showingAchievements {
showingAchievements = false
} else if showingMemberCenter {
showingMemberCenter = false
}
}
// MARK: - Deep Link Methods
func handleDeepLink(url: URL) {
//
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return }
switch components.host {
case "conversation":
if let conversationId = components.queryItems?.first(where: { $0.name == "id" })?.value,
let uuid = UUID(uuidString: conversationId) {
//
let conversation = MockDataManager.shared.conversations.first { $0.id == uuid }
navigateToChat(conversation: conversation)
}
case "topic":
if let topicId = components.queryItems?.first(where: { $0.name == "id" })?.value,
let uuid = UUID(uuidString: topicId) {
//
let topic = MockDataManager.shared.growthTopics.first { $0.id == uuid }
if let topic = topic {
navigateToTab(.growth)
navigateToTopic(topic)
}
}
case "location":
if let locationId = components.queryItems?.first(where: { $0.name == "id" })?.value,
let uuid = UUID(uuidString: locationId) {
//
let location = MockDataManager.shared.locationPins.first { $0.id == uuid }
if let location = location {
navigateToTab(.explore)
navigateToLocation(location)
}
}
case "post":
if let postId = components.queryItems?.first(where: { $0.name == "id" })?.value,
let uuid = UUID(uuidString: postId) {
//
let post = MockDataManager.shared.communityPosts.first { $0.id == uuid }
if let post = post {
navigateToTab(.explore)
navigateToPost(post)
}
}
default:
break
}
}
// MARK: - Loading Management
func showLoading(_ message: String = "加载中...") {
loadingMessage = message
withAnimation(.easeInOut) {
isLoading = true
}
}
func hideLoading() {
withAnimation(.easeInOut) {
isLoading = false
}
loadingMessage = ""
}
// MARK: - Chat Management
func sendMessage(_ content: String, to conversation: Conversation? = nil) {
guard !content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return }
let targetConversation = conversation ?? currentChatConversation
if let conv = targetConversation {
MockDataManager.shared.addMessage(to: conv.id, content: content, sender: .user)
} else {
//
let newConversation = MockDataManager.shared.createNewConversation()
currentChatConversation = newConversation
MockDataManager.shared.addMessage(to: newConversation.id, content: content, sender: .user)
}
chatInputText = ""
}
func toggleVoiceMode() {
withAnimation(.easeInOut(duration: 0.2)) {
isVoiceMode.toggle()
}
}
// MARK: - Utility Methods
func clearNavigationPath(for tab: MainTab) {
switch tab {
case .record:
recordNavigation = NavigationPath()
case .growth:
growthNavigation = NavigationPath()
case .explore:
exploreNavigation = NavigationPath()
case .insight:
insightNavigation = NavigationPath()
}
}
func popToRoot(for tab: MainTab) {
clearNavigationPath(for: tab)
dismissAllModals()
}
func canGoBack(for tab: MainTab) -> Bool {
switch tab {
case .record:
return !recordNavigation.isEmpty
case .growth:
return !growthNavigation.isEmpty
case .explore:
return !exploreNavigation.isEmpty
case .insight:
return !insightNavigation.isEmpty
}
}
func goBack(for tab: MainTab) {
switch tab {
case .record:
if !recordNavigation.isEmpty {
recordNavigation.removeLast()
}
case .growth:
if !growthNavigation.isEmpty {
growthNavigation.removeLast()
}
case .explore:
if !exploreNavigation.isEmpty {
exploreNavigation.removeLast()
}
case .insight:
if !insightNavigation.isEmpty {
insightNavigation.removeLast()
}
}
}
}
// MARK: - Main Tab Enum
enum MainTab: String, CaseIterable {
case record = "记录"
case growth = "治愈"
case explore = "探索"
case insight = "我的"
var title: String {
return self.rawValue
}
var icon: String {
switch self {
case .record: return "brain.head.profile"
case .growth: return "heart"
case .explore: return "map"
case .insight: return "person"
}
}
var selectedIcon: String {
switch self {
case .record: return "brain.head.profile.fill"
case .growth: return "heart.fill"
case .explore: return "map.fill"
case .insight: return "person.fill"
}
}
var color: Color {
switch self {
case .record: return .blue
case .growth: return .pink
case .explore: return .green
case .insight: return .orange
}
}
}
// MARK: - Navigation Destination Types
enum NavigationDestination: Hashable {
case conversation(Conversation)
case topic(GrowthTopic)
case location(LocationPin)
case post(CommunityPost)
case achievement(Achievement)
case settings
case profile
case chatHistory
case addLocation
static func == (lhs: NavigationDestination, rhs: NavigationDestination) -> Bool {
switch (lhs, rhs) {
case (.conversation(let lhsConv), .conversation(let rhsConv)):
return lhsConv.id == rhsConv.id
case (.topic(let lhsTopic), .topic(let rhsTopic)):
return lhsTopic.id == rhsTopic.id
case (.location(let lhsLoc), .location(let rhsLoc)):
return lhsLoc.id == rhsLoc.id
case (.post(let lhsPost), .post(let rhsPost)):
return lhsPost.id == rhsPost.id
case (.achievement(let lhsAch), .achievement(let rhsAch)):
return lhsAch.id == rhsAch.id
case (.settings, .settings),
(.profile, .profile),
(.chatHistory, .chatHistory),
(.addLocation, .addLocation):
return true
default:
return false
}
}
func hash(into hasher: inout Hasher) {
switch self {
case .conversation(let conv):
hasher.combine("conversation")
hasher.combine(conv.id)
case .topic(let topic):
hasher.combine("topic")
hasher.combine(topic.id)
case .location(let location):
hasher.combine("location")
hasher.combine(location.id)
case .post(let post):
hasher.combine("post")
hasher.combine(post.id)
case .achievement(let achievement):
hasher.combine("achievement")
hasher.combine(achievement.id)
case .settings:
hasher.combine("settings")
case .profile:
hasher.combine("profile")
case .chatHistory:
hasher.combine("chatHistory")
case .addLocation:
hasher.combine("addLocation")
}
}
}
@@ -1,565 +0,0 @@
//
// AnimationComponents.swift
// EmotionMuseum
//
// Created by on 2025/6/13.
//
import SwiftUI
// MARK: -
struct AnimationConfig {
static let springy = Animation.spring(response: 0.6, dampingFraction: 0.8)
static let bouncy = Animation.spring(response: 0.4, dampingFraction: 0.6)
static let smooth = Animation.easeInOut(duration: 0.3)
static let quick = Animation.easeOut(duration: 0.2)
static let slow = Animation.easeInOut(duration: 0.8)
static let elastic = Animation.interpolatingSpring(stiffness: 300, damping: 15)
}
// MARK: -
struct AnimatedAppearance: ViewModifier {
@State private var isVisible = false
let delay: Double
let animation: Animation
init(delay: Double = 0, animation: Animation = AnimationConfig.smooth) {
self.delay = delay
self.animation = animation
}
func body(content: Content) -> some View {
content
.scaleEffect(isVisible ? 1 : 0.8)
.opacity(isVisible ? 1 : 0)
.offset(y: isVisible ? 0 : 20)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
withAnimation(animation) {
isVisible = true
}
}
}
}
}
struct AnimatedCounter: ViewModifier {
@State private var currentValue: Int = 0
let targetValue: Int
let duration: Double
init(targetValue: Int, duration: Double = 1.0) {
self.targetValue = targetValue
self.duration = duration
}
func body(content: Content) -> some View {
Text("\(currentValue)")
.onAppear {
animateCounter()
}
.onChange(of: targetValue) { _ in
animateCounter()
}
}
private func animateCounter() {
currentValue = 0
let stepDuration = duration / Double(targetValue)
for i in 1...targetValue {
DispatchQueue.main.asyncAfter(deadline: .now() + stepDuration * Double(i)) {
withAnimation(.easeOut(duration: 0.1)) {
currentValue = i
}
}
}
}
}
// MARK: -
struct AnimatedButton<Content: View>: View {
let action: () -> Void
let content: () -> Content
@State private var isPressed = false
@State private var scale: CGFloat = 1.0
init(action: @escaping () -> Void, @ViewBuilder content: @escaping () -> Content) {
self.action = action
self.content = content
}
var body: some View {
Button(action: {
impactFeedback()
action()
}) {
content()
}
.scaleEffect(scale)
.onLongPressGesture(minimumDuration: 0, maximumDistance: .infinity) { isPressing in
withAnimation(AnimationConfig.quick) {
scale = isPressing ? 0.95 : 1.0
isPressed = isPressing
}
} perform: {
//
}
}
private func impactFeedback() {
let impactFeedback = UIImpactFeedbackGenerator(style: .light)
impactFeedback.impactOccurred()
}
}
// MARK: -
struct RippleEffect: View {
@State private var animationAmount = 1.0
let color: Color
let size: CGFloat
init(color: Color = .blue, size: CGFloat = 100) {
self.color = color
self.size = size
}
var body: some View {
Circle()
.fill(color.opacity(0.3))
.frame(width: size, height: size)
.scaleEffect(animationAmount)
.opacity(2 - animationAmount)
.animation(
Animation.easeOut(duration: 1.5)
.repeatForever(autoreverses: false),
value: animationAmount
)
.onAppear {
animationAmount = 2
}
}
}
// MARK: -
struct PulseEffect: ViewModifier {
@State private var isAnimating = false
let color: Color
let size: CGFloat
init(color: Color = .blue, size: CGFloat = 1.2) {
self.color = color
self.size = size
}
func body(content: Content) -> some View {
content
.scaleEffect(isAnimating ? size : 1.0)
.animation(
Animation.easeInOut(duration: 1.0)
.repeatForever(autoreverses: true),
value: isAnimating
)
.onAppear {
isAnimating = true
}
}
}
// MARK: -
struct ShakeEffect: ViewModifier {
@State private var shakeOffset: CGFloat = 0
let intensity: CGFloat
init(intensity: CGFloat = 10) {
self.intensity = intensity
}
func body(content: Content) -> some View {
content
.offset(x: shakeOffset)
.onAppear {
withAnimation(
Animation.easeInOut(duration: 0.1)
.repeatCount(4, autoreverses: true)
) {
shakeOffset = intensity
}
}
}
}
// MARK: -
struct FluidBackground: View {
@State private var animationOffset = 0.0
let colors: [Color]
init(colors: [Color] = [.blue, .purple, .pink]) {
self.colors = colors
}
var body: some View {
GeometryReader { geometry in
ZStack {
ForEach(0..<colors.count, id: \.self) { index in
Circle()
.fill(colors[index].opacity(0.3))
.frame(width: geometry.size.width * 0.8)
.offset(
x: sin(animationOffset + Double(index) * 2) * 50,
y: cos(animationOffset + Double(index) * 1.5) * 30
)
.blur(radius: 20)
}
}
}
.onAppear {
withAnimation(
Animation.linear(duration: 20)
.repeatForever(autoreverses: false)
) {
animationOffset = .pi * 2
}
}
}
}
// MARK: -
struct BounceListItem<Content: View>: View {
let content: () -> Content
@State private var isVisible = false
let index: Int
init(index: Int, @ViewBuilder content: @escaping () -> Content) {
self.index = index
self.content = content
}
var body: some View {
content()
.scaleEffect(isVisible ? 1 : 0.8)
.opacity(isVisible ? 1 : 0)
.offset(x: isVisible ? 0 : -50)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.1) {
withAnimation(AnimationConfig.bouncy) {
isVisible = true
}
}
}
}
}
// MARK: -
struct FlipCard<Front: View, Back: View>: View {
let front: () -> Front
let back: () -> Back
@State private var isFlipped = false
@State private var rotation = 0.0
init(@ViewBuilder front: @escaping () -> Front, @ViewBuilder back: @escaping () -> Back) {
self.front = front
self.back = back
}
var body: some View {
ZStack {
if !isFlipped {
front()
} else {
back()
.rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0))
}
}
.rotation3DEffect(.degrees(rotation), axis: (x: 0, y: 1, z: 0))
.onTapGesture {
withAnimation(.easeInOut(duration: 0.6)) {
rotation += 180
isFlipped.toggle()
}
}
}
}
// MARK: -
struct AnimatedProgressBar: View {
let progress: Double
let height: CGFloat
let backgroundColor: Color
let fillColor: Color
@State private var animatedProgress: Double = 0
init(
progress: Double,
height: CGFloat = 8,
backgroundColor: Color = Color.gray.opacity(0.3),
fillColor: Color = .blue
) {
self.progress = progress
self.height = height
self.backgroundColor = backgroundColor
self.fillColor = fillColor
}
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
Rectangle()
.fill(backgroundColor)
.frame(height: height)
.cornerRadius(height / 2)
Rectangle()
.fill(fillColor)
.frame(
width: geometry.size.width * animatedProgress,
height: height
)
.cornerRadius(height / 2)
.animation(AnimationConfig.smooth, value: animatedProgress)
}
}
.frame(height: height)
.onAppear {
withAnimation(.easeOut(duration: 1.0)) {
animatedProgress = progress
}
}
.onChange(of: progress) { newValue in
withAnimation(AnimationConfig.smooth) {
animatedProgress = newValue
}
}
}
}
// MARK: -
struct FloatingActionButton: View {
let action: () -> Void
let icon: String
let color: Color
@State private var isPressed = false
@State private var rotation = 0.0
init(icon: String, color: Color = .blue, action: @escaping () -> Void) {
self.icon = icon
self.color = color
self.action = action
}
var body: some View {
Button(action: {
withAnimation(AnimationConfig.bouncy) {
rotation += 360
}
action()
}) {
Image(systemName: icon)
.font(.title2)
.foregroundColor(.white)
.frame(width: 56, height: 56)
.background(color)
.clipShape(Circle())
.shadow(color: color.opacity(0.4), radius: 8, x: 0, y: 4)
}
.rotationEffect(.degrees(rotation))
.scaleEffect(isPressed ? 0.9 : 1.0)
.onLongPressGesture(minimumDuration: 0, maximumDistance: .infinity) { isPressing in
withAnimation(AnimationConfig.quick) {
isPressed = isPressing
}
} perform: {}
}
}
// MARK: -
struct ParticleSystem: View {
@State private var particles: [AnimationParticle] = []
let particleCount: Int
let colors: [Color]
init(particleCount: Int = 20, colors: [Color] = [.blue, .purple, .pink]) {
self.particleCount = particleCount
self.colors = colors
}
var body: some View {
GeometryReader { geometry in
ZStack {
ForEach(particles) { particle in
Circle()
.fill(particle.color)
.frame(width: particle.size, height: particle.size)
.position(particle.position)
.opacity(particle.opacity)
}
}
}
.onAppear {
createParticles()
animateParticles()
}
}
private func createParticles() {
particles = (0..<particleCount).map { _ in
AnimationParticle(
id: UUID(),
position: CGPoint(
x: CGFloat.random(in: 0...UIScreen.main.bounds.width),
y: CGFloat.random(in: 0...UIScreen.main.bounds.height)
),
size: CGFloat.random(in: 2...8),
color: colors.randomElement() ?? .blue,
opacity: Double.random(in: 0.3...1.0)
)
}
}
private func animateParticles() {
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
withAnimation(.linear(duration: 0.1)) {
for i in particles.indices {
particles[i].position.x += CGFloat.random(in: -2...2)
particles[i].position.y += CGFloat.random(in: -2...2)
particles[i].opacity = Double.random(in: 0.3...1.0)
}
}
}
}
}
struct AnimationParticle: Identifiable {
let id: UUID
var position: CGPoint
var size: CGFloat
var color: Color
var opacity: Double
}
// MARK: -
struct AnimatedGradientText: View {
let text: String
let colors: [Color]
@State private var animationOffset = 0.0
init(_ text: String, colors: [Color] = [.blue, .purple, .pink]) {
self.text = text
self.colors = colors
}
var body: some View {
Text(text)
.font(.title)
.fontWeight(.bold)
.foregroundColor(.primary)
.background(
LinearGradient(
colors: colors,
startPoint: .leading,
endPoint: .trailing
)
.rotationEffect(.degrees(animationOffset))
.mask(
Text(text)
.font(.title)
.fontWeight(.bold)
)
)
.onAppear {
withAnimation(
Animation.linear(duration: 3)
.repeatForever(autoreverses: false)
) {
animationOffset = 360
}
}
}
}
// MARK: -
extension View {
func animatedAppearance(delay: Double = 0, animation: Animation = AnimationConfig.smooth) -> some View {
modifier(AnimatedAppearance(delay: delay, animation: animation))
}
func pulseEffect(color: Color = .blue, size: CGFloat = 1.2) -> some View {
modifier(PulseEffect(color: color, size: size))
}
func shakeEffect(intensity: CGFloat = 10) -> some View {
modifier(ShakeEffect(intensity: intensity))
}
func bounceOnTap(scale: CGFloat = 0.95) -> some View {
scaleEffect(1.0)
.onTapGesture {
withAnimation(AnimationConfig.bouncy) {
//
}
}
}
func cardTransition() -> some View {
transition(
.asymmetric(
insertion: .scale(scale: 0.8).combined(with: .opacity),
removal: .scale(scale: 1.2).combined(with: .opacity)
)
)
}
func slideTransition(edge: Edge = .bottom) -> some View {
transition(.move(edge: edge).combined(with: .opacity))
}
func rotateTransition() -> some View {
transition(.scale(scale: 0.1).combined(with: .opacity))
}
}
// MARK: -
struct PageTransition<Content: View>: View {
let content: () -> Content
@State private var isVisible = false
let transitionType: TransitionType
enum TransitionType {
case slide, fade, scale, rotate
}
init(type: TransitionType = .fade, @ViewBuilder content: @escaping () -> Content) {
self.transitionType = type
self.content = content
}
var body: some View {
content()
.opacity(isVisible ? 1 : 0)
.scaleEffect(transitionType == .scale ? (isVisible ? 1 : 0.8) : 1)
.offset(y: transitionType == .slide ? (isVisible ? 0 : 50) : 0)
.rotationEffect(.degrees(transitionType == .rotate ? (isVisible ? 0 : 90) : 0))
.onAppear {
withAnimation(AnimationConfig.smooth) {
isVisible = true
}
}
}
}
// MARK: -
#Preview("动画组件") {
VStack(spacing: 20) {
AnimatedGradientText("情绪博物馆")
AnimatedProgressBar(progress: 0.7)
.frame(height: 10)
FloatingActionButton(icon: "plus") {
print("FAB tapped")
}
RippleEffect(color: .blue, size: 60)
}
.padding()
}
@@ -1,395 +0,0 @@
//
// ChakraHealingView.swift
// EmotionMuseum
//
// Created by on 2025/6/13.
//
import SwiftUI
import AVFoundation
struct ChakraHealingView: View {
let chakra: ChakraType
let onComplete: () -> Void
@State private var isPlaying = false
@State private var progress: Double = 0.0
@State private var timer: Timer? = nil
@State private var breathingPhase: BreathingPhase = .inhale
@State private var breathCount = 0
@State private var beforeScore: Int? = nil
@State private var afterScore: Int? = nil
@State private var showingCompletionSheet = false
//
@State private var particles: [Particle] = []
var body: some View {
ZStack {
//
chakra.color.opacity(0.2)
.ignoresSafeArea()
//
ZStack {
//
Circle()
.fill(
RadialGradient(
gradient: Gradient(colors: [chakra.color, chakra.color.opacity(0.5)]),
center: .center,
startRadius: 10,
endRadius: 150
)
)
.frame(width: breathingPhase == .inhale ? 200 : 150,
height: breathingPhase == .inhale ? 200 : 150)
.opacity(0.7)
.animation(.easeInOut(duration: breathingPhase == .inhale ? 4 : 4), value: breathingPhase)
//
ForEach(particles) { particle in
Circle()
.fill(chakra.color.opacity(particle.opacity))
.frame(width: particle.size, height: particle.size)
.position(particle.position)
}
}
//
VStack(spacing: 30) {
//
VStack(spacing: 8) {
Text(chakra.rawValue)
.font(.largeTitle)
.fontWeight(.bold)
.foregroundColor(chakra.color)
Text(chakra.description)
.font(.subheadline)
.foregroundColor(.secondary)
}
.padding(.top, 40)
Spacer()
//
Text(breathingPhase == .inhale ? "吸气..." : "呼气...")
.font(.title)
.fontWeight(.medium)
.foregroundColor(chakra.color)
.opacity(isPlaying ? 1 : 0)
Spacer()
//
VStack(spacing: 20) {
//
ProgressView(value: progress)
.progressViewStyle(LinearProgressViewStyle(tint: chakra.color))
.padding(.horizontal)
//
HStack(spacing: 40) {
Button(action: {
if beforeScore == nil {
//
showBeforeScorePrompt()
} else {
togglePlayback()
}
}) {
Image(systemName: isPlaying ? "pause.circle.fill" : "play.circle.fill")
.font(.system(size: 60))
.foregroundColor(chakra.color)
}
Button(action: {
completeSession()
}) {
Image(systemName: "xmark.circle.fill")
.font(.system(size: 40))
.foregroundColor(.gray)
}
}
.padding(.bottom, 30)
}
.background(
Rectangle()
.fill(Color(.systemBackground))
.cornerRadius(30, corners: [.topLeft, .topRight])
.shadow(color: Color.black.opacity(0.1), radius: 10, x: 0, y: -5)
)
}
}
.onAppear {
setupParticles()
}
.onDisappear {
stopSession()
}
.sheet(isPresented: $showingCompletionSheet) {
SessionCompletionView(chakra: chakra, beforeScore: beforeScore ?? 5, afterScore: afterScore ?? 5) {
onComplete()
}
}
}
// MARK: -
private func setupParticles() {
//
for _ in 0..<20 {
particles.append(Particle.random(in: UIScreen.main.bounds, color: chakra.color))
}
}
private func togglePlayback() {
isPlaying.toggle()
if isPlaying {
startSession()
} else {
pauseSession()
}
}
private func startSession() {
//
startBreathingAnimation()
//
animateParticles()
//
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
if progress < 1.0 {
progress += 0.0005 // 30
} else {
completeSession()
}
}
}
private func pauseSession() {
timer?.invalidate()
}
private func stopSession() {
timer?.invalidate()
timer = nil
}
private func startBreathingAnimation() {
//
Timer.scheduledTimer(withTimeInterval: 4, repeats: true) { _ in
withAnimation {
breathingPhase = breathingPhase == .inhale ? .exhale : .inhale
}
breathCount += 1
if breathCount >= 100 {
completeSession()
}
}
}
private func animateParticles() {
//
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
for i in 0..<particles.count {
if i < particles.count {
particles[i].move()
//
if !UIScreen.main.bounds.contains(particles[i].position) {
particles[i] = Particle.random(in: UIScreen.main.bounds, color: chakra.color)
}
}
}
}
}
private func showBeforeScorePrompt() {
//
//
beforeScore = 5
togglePlayback()
}
private func completeSession() {
stopSession()
isPlaying = false
//
afterScore = 8
//
showingCompletionSheet = true
}
}
// MARK: -
enum BreathingPhase {
case inhale
case exhale
}
// MARK: -
struct Particle: Identifiable {
let id = UUID()
var position: CGPoint
var direction: CGVector
var speed: CGFloat
var size: CGFloat
var opacity: Double
mutating func move() {
position.x += direction.dx * speed
position.y += direction.dy * speed
opacity = max(0, opacity - 0.001)
if opacity <= 0.1 {
opacity = Double.random(in: 0.3...0.7)
}
}
static func random(in rect: CGRect, color: Color) -> Particle {
let position = CGPoint(
x: CGFloat.random(in: rect.minX...rect.maxX),
y: CGFloat.random(in: rect.minY...rect.maxY)
)
let angle = CGFloat.random(in: 0...(2 * .pi))
let direction = CGVector(
dx: cos(angle),
dy: sin(angle)
)
return Particle(
position: position,
direction: direction,
speed: CGFloat.random(in: 0.5...2.0),
size: CGFloat.random(in: 3...8),
opacity: Double.random(in: 0.3...0.7)
)
}
}
// MARK: -
struct SessionCompletionView: View {
let chakra: ChakraType
let beforeScore: Int
let afterScore: Int
let onDismiss: () -> Void
var body: some View {
VStack(spacing: 20) {
//
Text("疗愈完成")
.font(.title)
.fontWeight(.bold)
//
HStack(spacing: 12) {
Circle()
.fill(chakra.color)
.frame(width: 30, height: 30)
Text(chakra.rawValue)
.font(.headline)
}
Divider()
//
VStack(alignment: .leading, spacing: 16) {
Text("疗愈效果")
.font(.headline)
HStack(spacing: 30) {
VStack {
Text("疗愈前")
.font(.subheadline)
.foregroundColor(.secondary)
Text("\(beforeScore)")
.font(.title)
.fontWeight(.bold)
}
Image(systemName: "arrow.right")
.font(.title2)
.foregroundColor(.secondary)
VStack {
Text("疗愈后")
.font(.subheadline)
.foregroundColor(.secondary)
Text("\(afterScore)")
.font(.title)
.fontWeight(.bold)
.foregroundColor(.green)
}
}
.padding()
.background(Color(.systemGray6))
.cornerRadius(12)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal)
//
VStack(alignment: .leading, spacing: 12) {
Text("反馈")
.font(.headline)
Text("你的\(chakra.rawValue)能量已得到提升,情绪状态有所改善。建议每天进行一次疗愈,保持能量平衡。")
.font(.body)
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal)
Spacer()
//
Button(action: {
onDismiss()
}) {
Text("返回")
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(chakra.color)
.cornerRadius(12)
}
.padding(.horizontal)
.padding(.bottom)
}
.padding(.top, 40)
}
}
// MARK: -
extension View {
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
clipShape(RoundedCorner(radius: radius, corners: corners))
}
}
struct RoundedCorner: Shape {
var radius: CGFloat = .infinity
var corners: UIRectCorner = .allCorners
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
return Path(path.cgPath)
}
}
#Preview {
ChakraHealingView(chakra: .heart, onComplete: {})
}
@@ -1,115 +0,0 @@
//
// ConversationPreviewCard.swift
// EmotionMuseum
//
// Created by on 2025/7/5.
//
import SwiftUI
struct ConversationPreviewCard: View {
let conversation: Conversation
let onTap: () -> Void
var body: some View {
Button(action: onTap) {
HStack(spacing: 12) {
//
Image(systemName: "message.circle.fill")
.font(.title2)
.foregroundColor(Color("AccentColor"))
VStack(alignment: .leading, spacing: 4) {
//
Text(conversation.title)
.font(.headline)
.foregroundColor(Color("PrimaryText"))
.lineLimit(1)
//
if let lastMessage = conversation.lastMessage {
Text(lastMessage.content)
.font(.subheadline)
.foregroundColor(Color("SecondaryText"))
.lineLimit(2)
} else if let summary = conversation.summary {
Text(summary)
.font(.subheadline)
.foregroundColor(Color("SecondaryText"))
.lineLimit(2)
} else {
Text("点击查看对话详情")
.font(.subheadline)
.foregroundColor(Color("TertiaryText"))
}
//
HStack {
Text(conversation.startTime.timeAgo)
.font(.caption)
.foregroundColor(Color("TertiaryText"))
Spacer()
Text("\(conversation.messageCount) 条消息")
.font(.caption)
.foregroundColor(Color("TertiaryText"))
}
}
Spacer()
//
if let emotion = conversation.emotionAnalysis?.primaryEmotion {
VStack {
Text(emotion.emoji)
.font(.title2)
Text(emotion.rawValue)
.font(.caption2)
.foregroundColor(emotion.color)
}
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.fill(Color("CardBackground"))
.shadow(
color: Color.black.opacity(0.05),
radius: 3,
x: 0,
y: 2
)
)
}
.buttonStyle(PlainButtonStyle())
}
}
#Preview {
ConversationPreviewCard(
conversation: Conversation(
userId: UUID(),
title: "今天的心情分享",
messages: [
Message(
conversationId: UUID(),
content: "我今天感觉有点焦虑,不知道该怎么办。",
type: .text,
sender: .user
)
],
emotionAnalysis: EmotionAnalysis(
primaryEmotion: .anxiety,
emotionIntensity: 0.7,
emotionTrend: .stable,
keywords: ["焦虑", "压力"],
aiInsights: "用户表现出一定程度的焦虑情绪"
)
)
) {
print("Tapped conversation")
}
.padding()
}
@@ -1,226 +0,0 @@
//
// ExploreView.swift
// EmotionMuseum
//
// Created by on 2025/6/13.
//
import SwiftUI
import MapKit
import CoreLocation
// MARK: -
struct ExploreView: View {
@EnvironmentObject var mockDataManager: MockDataManager
@EnvironmentObject var navigationManager: NavigationManager
@State private var selectedLocation: LocationPin?
@State private var showingLocationDetail = false
@State private var showingCommunityFeed = false
@State private var savedLocations: [LocationPin] = []
@State private var showingMapView = true
@State private var showingLocationPicker = false
@State private var shouldMoveToLocationPin = false
var body: some View {
NavigationView {
VStack(spacing: 0) {
//
HStack {
Button(action: {
showingMapView = true
showingCommunityFeed = false
}) {
HStack {
Image(systemName: "map")
Text("情绪地图")
}
.foregroundColor(showingMapView ? .white : .primary)
.padding(.horizontal, 16)
.padding(.vertical, 8)
.background(showingMapView ? Color.blue : Color.clear)
.cornerRadius(20)
}
Spacer()
Button(action: {
showingMapView = false
showingCommunityFeed = true
}) {
HStack {
Image(systemName: "person.3")
Text("社区分享")
}
.foregroundColor(showingCommunityFeed ? .white : .primary)
.padding(.horizontal, 16)
.padding(.vertical, 8)
.background(showingCommunityFeed ? Color.green : Color.clear)
.cornerRadius(20)
}
}
.padding()
.background(Color(.systemGray6))
//
if showingMapView {
mapViewSection
} else {
communityFeedSection
}
}
.navigationTitle("探索")
.navigationBarTitleDisplayMode(.large)
.onAppear {
loadSavedLocations()
}
}
.sheet(isPresented: $showingLocationDetail) {
if let location = selectedLocation {
LocationDetailView(location: location)
}
}
}
// MARK: -
private var mapViewSection: some View {
VStack {
//
ZStack {
MapView()
.frame(height: 300)
.cornerRadius(12)
.padding()
}
//
VStack(alignment: .leading, spacing: 12) {
HStack {
Text("推荐地点")
.font(.headline)
Spacer()
Button("查看全部") {
//
}
.font(.caption)
.foregroundColor(.blue)
}
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12) {
ForEach(Array(savedLocations.prefix(5))) { location in
LocationCard(location: location) {
selectedLocation = location
showingLocationDetail = true
}
}
}
.padding(.horizontal)
}
}
.padding()
Spacer()
}
}
// MARK: -
private var communityFeedSection: some View {
VStack {
ScrollView {
LazyVStack(spacing: 16) {
ForEach(mockDataManager.communityPosts) { post in
CommunityPostCard(post: post)
}
}
.padding()
}
}
}
// MARK: -
private func loadSavedLocations() {
//
savedLocations = [
LocationPin(
coordinate: Coordinate(latitude: 39.9042, longitude: 116.4074),
title: "天安门广场",
description: "庄严肃穆的历史地标",
type: .popular,
emotionTags: [.neutral],
category: .other
),
LocationPin(
coordinate: Coordinate(latitude: 39.9163, longitude: 116.3972),
title: "故宫博物院",
description: "深厚的历史文化底蕴",
type: .aiRecommended,
emotionTags: [.surprise],
category: .museum
),
LocationPin(
coordinate: Coordinate(latitude: 39.9925, longitude: 116.3135),
title: "颐和园",
description: "宁静优美的皇家园林",
type: .personal,
emotionTags: [.contentment],
category: .garden
),
LocationPin(
coordinate: Coordinate(latitude: 40.0090, longitude: 116.3348),
title: "圆明园",
description: "历史的见证与思考",
type: .community,
emotionTags: [.sadness],
category: .park
),
LocationPin(
coordinate: Coordinate(latitude: 39.9059, longitude: 116.3913),
title: "北海公园",
description: "古典园林的宁静之美",
type: .popular,
emotionTags: [.joy],
category: .park
)
]
}
}
// MARK: -
struct LocationCard: View {
let location: LocationPin
let onTap: () -> Void
var body: some View {
Button(action: onTap) {
VStack(alignment: .leading, spacing: 8) {
//
Rectangle()
.fill(Color(.systemGray5))
.frame(width: 120, height: 80)
.overlay(
Image(systemName: location.category.icon)
.font(.title)
.foregroundColor(.gray)
)
.cornerRadius(8)
VStack(alignment: .leading, spacing: 4) {
Text(location.title)
.font(.caption)
.fontWeight(.medium)
.lineLimit(1)
Text(location.description)
.font(.caption2)
.foregroundColor(.secondary)
.lineLimit(2)
}
.frame(width: 120, alignment: .leading)
}
}
.buttonStyle(PlainButtonStyle())
}
}
#Preview {
ExploreView()
.environmentObject(MockDataManager.shared)
.environmentObject(NavigationManager())
}
@@ -1,808 +0,0 @@
//
// GrowthView.swift
// EmotionMuseum
//
// Created by on 2025/6/13.
//
import SwiftUI
// MARK: -
struct GrowthView: View {
@StateObject private var themeManager = ThemeManager()
@State private var showingTopicDetail = false
@State private var selectedTopic: GrowthTopic?
@State private var showingInsights = false
@State private var showingRadarChart = false
@State private var loadingState: LoadingState = .idle
@State private var isInitialLoading = true
var body: some View {
NavigationView {
LoadingStateView(loadingState: isInitialLoading ? .loading : .loaded) {
ScrollView {
VStack(spacing: 24) {
//
EmotionalInsightCard {
showingInsights = true
}
.transition(.scale(scale: 0.8).combined(with: .opacity))
//
UserProfileRadarCard {
showingRadarChart = true
}
.transition(.scale(scale: 0.8).combined(with: .opacity))
//
GrowthTopicsSection { topic in
selectedTopic = topic
showingTopicDetail = true
}
.transition(.scale(scale: 0.8).combined(with: .opacity))
//
TodayActionCard()
.transition(.scale(scale: 0.8).combined(with: .opacity))
//
GrowthTimelineCard()
.transition(.scale(scale: 0.8).combined(with: .opacity))
}
.padding(.horizontal)
.padding(.vertical)
}
.refreshable {
await refreshData()
}
} loadingView: {
AnyView(growthViewSkeleton)
}
.navigationTitle("成长治愈")
.navigationBarTitleDisplayMode(.large)
}
.environmentObject(themeManager)
.preferredColorScheme(themeManager.systemFollowsDeviceTheme ? nil : (themeManager.isDarkMode ? .dark : .light))
.sheet(isPresented: $showingTopicDetail) {
if let topic = selectedTopic {
TopicDetailView(topic: topic)
.environmentObject(themeManager)
}
}
.sheet(isPresented: $showingInsights) {
EmotionalInsightsView()
.environmentObject(themeManager)
}
.sheet(isPresented: $showingRadarChart) {
NavigationView {
VStack {
Text("用户画像雷达图")
.font(.title)
.padding()
Text("这里显示用户的多维度能力雷达图")
.foregroundColor(.secondary)
.padding()
Spacer()
}
.navigationTitle("用户画像")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("关闭") {
showingRadarChart = false
}
}
}
}
.environmentObject(themeManager)
}
.onAppear {
simulateInitialLoading()
}
}
// MARK: -
private func simulateInitialLoading() {
loadingState = .loading
//
DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) {
withAnimation(.easeOut(duration: 0.8)) {
isInitialLoading = false
loadingState = .loaded
}
}
}
private func refreshData() async {
loadingState = .loading
//
try? await Task.sleep(nanoseconds: 800_000_000) // 0.8
DispatchQueue.main.async {
withAnimation(.easeOut(duration: 0.6)) {
loadingState = .loaded
}
}
}
// MARK: -
private var growthViewSkeleton: some View {
ScrollView {
VStack(spacing: 24) {
//
insightCardSkeleton
//
radarCardSkeleton
//
topicsGridSkeleton
//
actionCardSkeleton
//
timelineCardSkeleton
}
.padding(.horizontal)
.padding(.vertical)
}
.background(Color.theme.background)
}
private var insightCardSkeleton: some View {
VStack(alignment: .leading, spacing: 16) {
HStack {
VStack(alignment: .leading, spacing: 4) {
SkeletonView(width: 80, height: 20, cornerRadius: 6)
SkeletonView(width: 140, height: 12, cornerRadius: 3)
}
Spacer()
SkeletonView(width: 24, height: 24, cornerRadius: 12)
}
VStack(spacing: 12) {
ForEach(0..<3, id: \.self) { _ in
HStack(spacing: 12) {
SkeletonView(width: 20, height: 16, cornerRadius: 4)
SkeletonView(width: 80, height: 14, cornerRadius: 3)
Spacer()
SkeletonView(width: 60, height: 14, cornerRadius: 3)
}
}
}
HStack {
SkeletonView(width: 100, height: 12, cornerRadius: 3)
Spacer()
SkeletonView(width: 12, height: 12, cornerRadius: 3)
}
}
.padding()
.background(Color.theme.cardBackground)
.cornerRadius(16)
}
private var radarCardSkeleton: some View {
VStack(spacing: 16) {
HStack {
SkeletonView(width: 80, height: 20, cornerRadius: 6)
Spacer()
SkeletonView(width: 24, height: 24, cornerRadius: 12)
}
VStack(spacing: 8) {
ForEach(0..<5, id: \.self) { _ in
HStack {
SkeletonView(width: 60, height: 12, cornerRadius: 3)
SkeletonView(height: 8, cornerRadius: 4)
SkeletonView(width: 40, height: 12, cornerRadius: 3)
}
}
}
HStack {
SkeletonView(width: 120, height: 12, cornerRadius: 3)
Spacer()
SkeletonView(width: 12, height: 12, cornerRadius: 3)
}
}
.padding()
.background(Color.theme.cardBackground)
.cornerRadius(16)
}
private var topicsGridSkeleton: some View {
VStack(alignment: .leading, spacing: 16) {
HStack {
SkeletonView(width: 80, height: 20, cornerRadius: 6)
Spacer()
SkeletonView(width: 60, height: 12, cornerRadius: 3)
}
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 2), spacing: 12) {
ForEach(0..<4, id: \.self) { _ in
VStack(alignment: .leading, spacing: 12) {
HStack {
SkeletonView(width: 20, height: 20, cornerRadius: 10)
Spacer()
SkeletonView(width: 30, height: 12, cornerRadius: 3)
}
VStack(alignment: .leading, spacing: 4) {
SkeletonView(width: 100, height: 14, cornerRadius: 3)
SkeletonView(width: 120, height: 12, cornerRadius: 3)
SkeletonView(width: 80, height: 12, cornerRadius: 3)
}
VStack(spacing: 4) {
HStack {
SkeletonView(width: 30, height: 10, cornerRadius: 2)
Spacer()
SkeletonView(width: 30, height: 10, cornerRadius: 2)
}
SkeletonView(height: 4, cornerRadius: 2)
}
}
.padding()
.background(Color.theme.cardBackground)
.cornerRadius(12)
}
}
}
}
private var actionCardSkeleton: some View {
VStack(alignment: .leading, spacing: 16) {
HStack {
SkeletonView(width: 80, height: 20, cornerRadius: 6)
Spacer()
SkeletonView(width: 24, height: 24, cornerRadius: 12)
}
VStack(spacing: 12) {
ForEach(0..<3, id: \.self) { _ in
HStack(spacing: 12) {
SkeletonView(width: 24, height: 24, cornerRadius: 12)
VStack(alignment: .leading, spacing: 2) {
SkeletonView(width: 160, height: 14, cornerRadius: 3)
HStack(spacing: 8) {
SkeletonView(width: 60, height: 16, cornerRadius: 8)
SkeletonView(width: 40, height: 12, cornerRadius: 3)
}
}
Spacer()
}
}
}
}
.padding()
.background(Color.theme.cardBackground)
.cornerRadius(16)
}
private var timelineCardSkeleton: some View {
VStack(alignment: .leading, spacing: 16) {
SkeletonView(width: 80, height: 20, cornerRadius: 6)
VStack(alignment: .leading, spacing: 12) {
ForEach(0..<4, id: \.self) { _ in
HStack(spacing: 12) {
VStack {
SkeletonView(width: 8, height: 8, cornerRadius: 4)
SkeletonView(width: 1, height: 20, cornerRadius: 1)
}
VStack(alignment: .leading, spacing: 2) {
HStack {
SkeletonView(width: 100, height: 14, cornerRadius: 3)
Spacer()
SkeletonView(width: 40, height: 12, cornerRadius: 3)
}
SkeletonView(width: 180, height: 12, cornerRadius: 3)
}
}
}
}
}
.padding()
.background(Color.theme.cardBackground)
.cornerRadius(16)
}
}
// MARK: -
struct EmotionalInsightCard: View {
let onTap: () -> Void
var body: some View {
Button(action: onTap) {
VStack(alignment: .leading, spacing: 16) {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text("情绪洞察")
.font(.headline)
.fontWeight(.semibold)
.foregroundColor(Color.theme.primaryText)
Text("基于你的对话记录生成")
.font(.caption)
.foregroundColor(Color.theme.secondaryText)
}
Spacer()
Image(systemName: "brain.head.profile")
.font(.title2)
.foregroundColor(.purple)
}
VStack(alignment: .leading, spacing: 12) {
InsightRow(
icon: "heart.fill",
title: "主要情绪状态",
value: "平静自省",
color: .blue
)
InsightRow(
icon: "target",
title: "成长焦点",
value: "人际关系",
color: .green
)
InsightRow(
icon: "chart.line.uptrend.xyaxis",
title: "进步指数",
value: "↗️ 稳步提升",
color: .orange
)
}
HStack {
Text("点击查看详细分析")
.font(.caption)
.foregroundColor(Color.theme.secondaryText)
Spacer()
Image(systemName: "chevron.right")
.font(.caption)
.foregroundColor(Color.theme.secondaryText)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 16)
.fill(Color(.systemGray6))
)
}
.buttonStyle(PlainButtonStyle())
}
}
struct InsightRow: View {
let icon: String
let title: String
let value: String
let color: Color
var body: some View {
HStack(spacing: 12) {
Image(systemName: icon)
.font(.subheadline)
.foregroundColor(color)
.frame(width: 20)
Text(title)
.font(.subheadline)
.foregroundColor(Color.theme.secondaryText)
Spacer()
Text(value)
.font(.subheadline)
.fontWeight(.medium)
}
}
}
// MARK: -
struct UserProfileRadarCard: View {
let onTap: () -> Void
var body: some View {
Button(action: onTap) {
VStack(spacing: 16) {
HStack {
Text("成长画像")
.font(.headline)
.fontWeight(.semibold)
Spacer()
Image(systemName: "person.crop.circle.badge.checkmark")
.font(.title2)
.foregroundColor(.green)
}
//
VStack(spacing: 8) {
ProfileDimensionRow(name: "自我感知", value: 0.8, color: .blue)
ProfileDimensionRow(name: "情绪韧性", value: 0.7, color: .purple)
ProfileDimensionRow(name: "行动力", value: 0.6, color: .orange)
ProfileDimensionRow(name: "共情力", value: 0.9, color: .green)
ProfileDimensionRow(name: "生活热度", value: 0.7, color: .red)
}
HStack {
Text("点击查看完整雷达图")
.font(.caption)
.foregroundColor(.secondary)
Spacer()
Image(systemName: "chevron.right")
.font(.caption)
.foregroundColor(.secondary)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 16)
.fill(Color(.systemGray6))
)
}
.buttonStyle(PlainButtonStyle())
}
}
struct ProfileDimensionRow: View {
let name: String
let value: Double
let color: Color
var body: some View {
HStack {
Text(name)
.font(.caption)
.foregroundColor(.primary)
.frame(width: 60, alignment: .leading)
ProgressView(value: value)
.progressViewStyle(LinearProgressViewStyle(tint: color))
Text("\(Int(value * 100))%")
.font(.caption)
.fontWeight(.medium)
.foregroundColor(color)
.frame(width: 40, alignment: .trailing)
}
}
}
// MARK: -
struct GrowthTopicsSection: View {
let onTopicTap: (GrowthTopic) -> Void
var body: some View {
VStack(alignment: .leading, spacing: 16) {
HStack {
Text("成长课题")
.font(.headline)
.fontWeight(.semibold)
Spacer()
Text("3个进行中")
.font(.caption)
.foregroundColor(.secondary)
}
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 2), spacing: 12) {
ForEach(sampleGrowthTopics) { topic in
TopicCard(topic: topic) {
onTopicTap(topic)
}
}
}
}
}
}
struct TopicCard: View {
let topic: GrowthTopic
let onTap: () -> Void
var body: some View {
Button(action: onTap) {
VStack(alignment: .leading, spacing: 12) {
HStack {
Image(systemName: topic.icon)
.font(.title3)
.foregroundColor(topic.color)
Spacer()
Text("Lv.\(topic.level)")
.font(.caption)
.fontWeight(.bold)
.foregroundColor(topic.color)
}
VStack(alignment: .leading, spacing: 4) {
Text(topic.title)
.font(.subheadline)
.fontWeight(.medium)
.multilineTextAlignment(.leading)
Text(topic.description)
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(2)
.multilineTextAlignment(.leading)
}
VStack(spacing: 4) {
HStack {
Text("进度")
.font(.caption2)
.foregroundColor(.secondary)
Spacer()
Text("\(Int(topic.progress * 100))%")
.font(.caption2)
.fontWeight(.medium)
}
ProgressView(value: topic.progress)
.progressViewStyle(LinearProgressViewStyle(tint: topic.color))
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.fill(Color(.systemGray6))
)
}
.buttonStyle(PlainButtonStyle())
}
}
// MARK: -
struct TodayActionCard: View {
var body: some View {
VStack(alignment: .leading, spacing: 16) {
HStack {
Text("今日推荐")
.font(.headline)
.fontWeight(.semibold)
Spacer()
Image(systemName: "lightbulb.fill")
.font(.title3)
.foregroundColor(.yellow)
}
VStack(spacing: 12) {
ActionItemView(
title: "与朋友分享一件开心的事",
category: "人际关系",
duration: "5分钟",
color: .blue
)
ActionItemView(
title: "写下今天的三个感恩点",
category: "自我觉察",
duration: "10分钟",
color: .green
)
ActionItemView(
title: "深呼吸练习",
category: "情绪调节",
duration: "3分钟",
color: .purple
)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 16)
.fill(Color(.systemGray6))
)
}
}
struct ActionItemView: View {
let title: String
let category: String
let duration: String
let color: Color
@State private var isCompleted = false
var body: some View {
HStack(spacing: 12) {
Button(action: { isCompleted.toggle() }) {
Image(systemName: isCompleted ? "checkmark.circle.fill" : "circle")
.font(.title3)
.foregroundColor(isCompleted ? color : .gray)
}
VStack(alignment: .leading, spacing: 2) {
Text(title)
.font(.subheadline)
.fontWeight(.medium)
.strikethrough(isCompleted)
HStack(spacing: 8) {
Text(category)
.font(.caption)
.padding(.horizontal, 8)
.padding(.vertical, 2)
.background(color.opacity(0.2))
.foregroundColor(color)
.cornerRadius(8)
Text(duration)
.font(.caption)
.foregroundColor(.secondary)
}
}
Spacer()
}
.opacity(isCompleted ? 0.6 : 1.0)
.animation(.easeInOut(duration: 0.2), value: isCompleted)
}
}
// MARK: -
struct GrowthTimelineCard: View {
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("成长历程")
.font(.headline)
.fontWeight(.semibold)
VStack(alignment: .leading, spacing: 12) {
TimelineItem(
date: "今天",
title: "完成情绪记录",
description: "记录了平静的心情状态",
color: .blue,
isRecent: true
)
TimelineItem(
date: "昨天",
title: "课题升级",
description: "人际关系课题提升到Lv.2",
color: .green,
isRecent: true
)
TimelineItem(
date: "3天前",
title: "解锁新课题",
description: "开始学习情绪调节技巧",
color: .purple,
isRecent: false
)
TimelineItem(
date: "1周前",
title: "达成里程碑",
description: "连续7天完成情绪记录",
color: .orange,
isRecent: false
)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 16)
.fill(Color(.systemGray6))
)
}
}
struct TimelineItem: View {
let date: String
let title: String
let description: String
let color: Color
let isRecent: Bool
var body: some View {
HStack(spacing: 12) {
VStack {
Circle()
.fill(color)
.frame(width: 8, height: 8)
if !isRecent {
Rectangle()
.fill(Color(.systemGray4))
.frame(width: 1, height: 20)
}
}
VStack(alignment: .leading, spacing: 2) {
HStack {
Text(title)
.font(.subheadline)
.fontWeight(.medium)
Spacer()
Text(date)
.font(.caption)
.foregroundColor(.secondary)
}
Text(description)
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(2)
}
}
.opacity(isRecent ? 1.0 : 0.7)
}
}
// MARK: - GrowthTopic UI
extension GrowthTopic {
var icon: String {
category.icon
}
var color: Color {
category.color
}
}
// MARK: -
let sampleGrowthTopics = [
GrowthTopic(
title: "人际关系边界",
description: "学习建立健康的人际关系边界",
category: .relationships,
difficulty: .intermediate,
progress: 0.7,
level: 2
),
GrowthTopic(
title: "情绪调节技能",
description: "掌握情绪识别和调节的核心技能",
category: .emotionRegulation,
difficulty: .beginner,
progress: 0.3,
level: 1
),
GrowthTopic(
title: "深度自我认知",
description: "提升对内在世界的认知和理解",
category: .selfAwareness,
difficulty: .advanced,
progress: 0.9,
level: 3
),
GrowthTopic(
title: "行动力提升",
description: "培养执行力和目标达成能力",
category: .lifeGoals,
difficulty: .beginner,
progress: 0.4,
level: 1
)
]
@@ -1,311 +0,0 @@
//
// HealingView.swift
// EmotionMuseum
//
// Created by on 2025/6/13.
//
import SwiftUI
import AVFoundation
struct HealingView: View {
@State private var selectedChakra: ChakraType? = nil
@State private var showingHealingSession = false
@State private var chakraStates: [ChakraType: ChakraState] = [:]
var body: some View {
NavigationView {
ZStack {
//
LinearGradient(
gradient: Gradient(colors: [.purple.opacity(0.1), .blue.opacity(0.1)]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
ScrollView {
VStack(spacing: 30) {
//
VStack(spacing: 8) {
Text("脉轮疗愈")
.font(.largeTitle)
.fontWeight(.bold)
Text("点击脉轮区域开始疗愈之旅")
.font(.subheadline)
.foregroundColor(.secondary)
}
.padding(.top)
//
VStack(spacing: 0) {
//
ChakraButton(
chakra: .crown,
state: chakraStates[.crown] ?? .normal,
action: { selectChakra(.crown) }
)
.offset(y: 10)
Spacer().frame(height: 20)
//
ChakraButton(
chakra: .thirdEye,
state: chakraStates[.thirdEye] ?? .normal,
action: { selectChakra(.thirdEye) }
)
Spacer().frame(height: 30)
//
ChakraButton(
chakra: .throat,
state: chakraStates[.throat] ?? .weak,
action: { selectChakra(.throat) }
)
Spacer().frame(height: 40)
//
ChakraButton(
chakra: .heart,
state: chakraStates[.heart] ?? .normal,
action: { selectChakra(.heart) }
)
Spacer().frame(height: 40)
//
ChakraButton(
chakra: .solarPlexus,
state: chakraStates[.solarPlexus] ?? .normal,
action: { selectChakra(.solarPlexus) }
)
Spacer().frame(height: 40)
//
ChakraButton(
chakra: .sacral,
state: chakraStates[.sacral] ?? .weak,
action: { selectChakra(.sacral) }
)
Spacer().frame(height: 40)
//
ChakraButton(
chakra: .root,
state: chakraStates[.root] ?? .normal,
action: { selectChakra(.root) }
)
}
.frame(maxWidth: 200)
//
VStack(alignment: .leading, spacing: 12) {
Text("脉轮状态说明")
.font(.headline)
HStack(spacing: 16) {
HStack {
Circle()
.fill(Color.green)
.frame(width: 12, height: 12)
Text("健康")
.font(.caption)
}
HStack {
Circle()
.fill(Color.orange.opacity(0.6))
.frame(width: 12, height: 12)
Text("疲弱")
.font(.caption)
}
HStack {
Circle()
.fill(Color.red.opacity(0.6))
.frame(width: 12, height: 12)
Text("受阻")
.font(.caption)
}
}
}
.padding()
.background(Color(.systemGray6))
.cornerRadius(12)
.padding(.horizontal)
//
VStack(alignment: .leading, spacing: 12) {
Text("快速疗愈")
.font(.headline)
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 2), spacing: 12) {
QuickHealingCard(title: "全身平衡", icon: "figure.mind.and.body", color: .purple)
QuickHealingCard(title: "情绪释放", icon: "heart.fill", color: .pink)
QuickHealingCard(title: "能量充电", icon: "bolt.fill", color: .yellow)
QuickHealingCard(title: "深度放松", icon: "moon.fill", color: .blue)
}
}
.padding()
.background(Color(.systemGray6))
.cornerRadius(12)
.padding(.horizontal)
}
.padding(.bottom, 30)
}
}
.navigationBarHidden(true)
}
.sheet(item: $selectedChakra) { chakra in
ChakraHealingView(chakra: chakra) {
//
updateChakraState(chakra, newState: .normal)
}
}
.onAppear {
initializeChakraStates()
}
}
private func selectChakra(_ chakra: ChakraType) {
selectedChakra = chakra
}
private func initializeChakraStates() {
//
chakraStates = [
.root: .normal,
.sacral: .weak,
.solarPlexus: .normal,
.heart: .normal,
.throat: .weak,
.thirdEye: .normal,
.crown: .normal
]
}
private func updateChakraState(_ chakra: ChakraType, newState: ChakraState) {
withAnimation {
chakraStates[chakra] = newState
}
}
}
// MARK: -
extension ChakraType: Identifiable {
var id: String { rawValue }
}
// MARK: -
enum ChakraState {
case normal //
case weak //
case blocked //
var opacity: Double {
switch self {
case .normal: return 1.0
case .weak: return 0.6
case .blocked: return 0.3
}
}
}
// MARK: -
struct ChakraButton: View {
let chakra: ChakraType
let state: ChakraState
let action: () -> Void
@State private var isAnimating = false
var body: some View {
Button(action: action) {
ZStack {
//
Circle()
.fill(
RadialGradient(
gradient: Gradient(colors: [chakra.color.opacity(0.3), Color.clear]),
center: .center,
startRadius: 20,
endRadius: 40
)
)
.frame(width: 80, height: 80)
.scaleEffect(isAnimating ? 1.2 : 1.0)
.opacity(state == .weak ? 0.8 : 1.0)
//
Circle()
.fill(
RadialGradient(
gradient: Gradient(colors: [chakra.color, chakra.color.opacity(0.7)]),
center: .center,
startRadius: 5,
endRadius: 25
)
)
.frame(width: 50, height: 50)
.opacity(state.opacity)
.overlay(
Circle()
.stroke(Color.white.opacity(0.8), lineWidth: 2)
)
//
Text(chakra.rawValue)
.font(.caption2)
.fontWeight(.semibold)
.foregroundColor(.white)
.shadow(radius: 1)
}
}
.onAppear {
if state == .weak {
withAnimation(Animation.easeInOut(duration: 2).repeatForever(autoreverses: true)) {
isAnimating = true
}
}
}
}
}
// MARK: -
struct QuickHealingCard: View {
let title: String
let icon: String
let color: Color
var body: some View {
Button(action: {
//
}) {
VStack(spacing: 8) {
Image(systemName: icon)
.font(.title2)
.foregroundColor(color)
Text(title)
.font(.subheadline)
.fontWeight(.medium)
.foregroundColor(.primary)
}
.frame(maxWidth: .infinity)
.padding()
.background(Color(.systemBackground))
.cornerRadius(12)
.shadow(color: color.opacity(0.3), radius: 4, x: 0, y: 2)
}
}
}
#Preview {
HealingView()
}
File diff suppressed because it is too large Load Diff
@@ -1,438 +0,0 @@
//
// LoadingComponents.swift
// EmotionMuseum
//
// Created by on 2025/6/13.
//
import SwiftUI
// MARK: -
enum LoadingState {
case idle
case loading
case loaded
case error(String)
}
// MARK: -
class ThemeManager: ObservableObject {
@Published var isDarkMode: Bool = false
@Published var systemFollowsDeviceTheme: Bool = true
init() {
//
self.isDarkMode = UserDefaults.standard.bool(forKey: "darkMode")
self.systemFollowsDeviceTheme = UserDefaults.standard.bool(forKey: "systemFollowsDeviceTheme")
}
func toggleTheme() {
isDarkMode.toggle()
UserDefaults.standard.set(isDarkMode, forKey: "darkMode")
}
func setSystemFollowing(_ follows: Bool) {
systemFollowsDeviceTheme = follows
UserDefaults.standard.set(follows, forKey: "systemFollowsDeviceTheme")
}
}
// MARK: -
extension Color {
static let theme = ColorTheme()
}
struct ColorTheme {
//
let primary = Color("PrimaryColor")
let secondary = Color("SecondaryColor")
let accent = Color("AccentColor")
//
let background = Color("BackgroundColor")
let cardBackground = Color("CardBackground")
let surfaceBackground = Color("SurfaceBackground")
//
let primaryText = Color("PrimaryText")
let secondaryText = Color("SecondaryText")
let tertiaryText = Color("TertiaryText")
//
let border = Color("BorderColor")
let divider = Color("DividerColor")
//
let success = Color("SuccessColor")
let warning = Color("WarningColor")
let error = Color("ErrorColor")
//
let skeleton = Color("SkeletonColor")
let skeletonHighlight = Color("SkeletonHighlight")
}
// MARK: -
struct SkeletonView: View {
@State private var isAnimating = false
let width: CGFloat?
let height: CGFloat
let cornerRadius: CGFloat
init(width: CGFloat? = nil, height: CGFloat = 20, cornerRadius: CGFloat = 8) {
self.width = width
self.height = height
self.cornerRadius = cornerRadius
}
var body: some View {
Rectangle()
.fill(
LinearGradient(
colors: [
Color.theme.skeleton,
Color.theme.skeletonHighlight,
Color.theme.skeleton
],
startPoint: .leading,
endPoint: .trailing
)
.opacity(isAnimating ? 0.6 : 1.0)
)
.frame(width: width, height: height)
.cornerRadius(cornerRadius)
.onAppear {
withAnimation(
Animation
.easeInOut(duration: 1.2)
.repeatForever(autoreverses: true)
) {
isAnimating = true
}
}
}
}
// MARK: -
struct SkeletonText: View {
let lineCount: Int
let spacing: CGFloat
init(lineCount: Int = 3, spacing: CGFloat = 8) {
self.lineCount = lineCount
self.spacing = spacing
}
var body: some View {
VStack(alignment: .leading, spacing: spacing) {
ForEach(0..<lineCount, id: \.self) { index in
SkeletonView(
width: index == lineCount - 1 ? CGFloat.random(in: 100...200) : nil,
height: 16,
cornerRadius: 4
)
}
}
}
}
// MARK: -
struct SkeletonCard: View {
let showImage: Bool
let showButton: Bool
init(showImage: Bool = true, showButton: Bool = false) {
self.showImage = showImage
self.showButton = showButton
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
if showImage {
SkeletonView(height: 120, cornerRadius: 12)
}
VStack(alignment: .leading, spacing: 8) {
SkeletonView(height: 20, cornerRadius: 6)
SkeletonText(lineCount: 2, spacing: 6)
if showButton {
HStack {
Spacer()
SkeletonView(width: 80, height: 32, cornerRadius: 16)
}
}
}
.padding(.horizontal, showImage ? 0 : 16)
}
.padding(16)
.background(Color.theme.cardBackground)
.cornerRadius(16)
.shadow(color: Color.black.opacity(0.05), radius: 8, x: 0, y: 2)
}
}
// MARK: -
struct SkeletonListItem: View {
let showAvatar: Bool
let showTrailing: Bool
init(showAvatar: Bool = true, showTrailing: Bool = true) {
self.showAvatar = showAvatar
self.showTrailing = showTrailing
}
var body: some View {
HStack(spacing: 12) {
if showAvatar {
SkeletonView(width: 50, height: 50, cornerRadius: 25)
}
VStack(alignment: .leading, spacing: 6) {
SkeletonView(width: 120, height: 16, cornerRadius: 4)
SkeletonView(width: 200, height: 14, cornerRadius: 4)
}
Spacer()
if showTrailing {
SkeletonView(width: 60, height: 20, cornerRadius: 6)
}
}
.padding(.horizontal, 16)
.padding(.vertical, 12)
}
}
// MARK: -
struct LoadingStateView<Content: View>: View {
let loadingState: LoadingState
let content: () -> Content
let loadingView: (() -> AnyView)?
let errorView: ((String) -> AnyView)?
init(
loadingState: LoadingState,
@ViewBuilder content: @escaping () -> Content,
loadingView: (() -> AnyView)? = nil,
errorView: ((String) -> AnyView)? = nil
) {
self.loadingState = loadingState
self.content = content
self.loadingView = loadingView
self.errorView = errorView
}
var body: some View {
switch loadingState {
case .idle:
Color.clear
.onAppear {
//
}
case .loading:
if let loadingView = loadingView {
loadingView()
} else {
defaultLoadingView
}
case .loaded:
content()
.transition(.opacity.combined(with: .scale(scale: 0.95)))
case .error(let message):
if let errorView = errorView {
errorView(message)
} else {
defaultErrorView(message: message)
}
}
}
private var defaultLoadingView: some View {
VStack(spacing: 20) {
ProgressView()
.scaleEffect(1.5)
.progressViewStyle(CircularProgressViewStyle(tint: Color.theme.accent))
Text("加载中...")
.font(.subheadline)
.foregroundColor(Color.theme.secondaryText)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.theme.background)
}
private func defaultErrorView(message: String) -> some View {
VStack(spacing: 16) {
Image(systemName: "exclamationmark.triangle")
.font(.system(size: 48))
.foregroundColor(Color.theme.error)
Text("加载失败")
.font(.headline)
.foregroundColor(Color.theme.primaryText)
Text(message)
.font(.subheadline)
.foregroundColor(Color.theme.secondaryText)
.multilineTextAlignment(.center)
Button("重试") {
//
}
.buttonStyle(PrimaryButtonStyle())
}
.padding(32)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.theme.background)
}
}
// MARK: -
struct PrimaryButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.padding(.horizontal, 24)
.padding(.vertical, 12)
.background(Color.theme.accent)
.foregroundColor(.white)
.cornerRadius(12)
.scaleEffect(configuration.isPressed ? 0.95 : 1.0)
.animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
}
}
// MARK: -
struct LoadingList: View {
let itemCount: Int
let showAvatar: Bool
let showTrailing: Bool
init(itemCount: Int = 5, showAvatar: Bool = true, showTrailing: Bool = true) {
self.itemCount = itemCount
self.showAvatar = showAvatar
self.showTrailing = showTrailing
}
var body: some View {
LazyVStack(spacing: 0) {
ForEach(0..<itemCount, id: \.self) { _ in
SkeletonListItem(showAvatar: showAvatar, showTrailing: showTrailing)
Divider()
.background(Color.theme.divider)
}
}
}
}
// MARK: -
struct LoadingCardGrid: View {
let columns: Int
let itemCount: Int
let showImage: Bool
let showButton: Bool
init(columns: Int = 2, itemCount: Int = 6, showImage: Bool = true, showButton: Bool = false) {
self.columns = columns
self.itemCount = itemCount
self.showImage = showImage
self.showButton = showButton
}
var body: some View {
LazyVGrid(
columns: Array(repeating: GridItem(.flexible(), spacing: 16), count: columns),
spacing: 16
) {
ForEach(0..<itemCount, id: \.self) { _ in
SkeletonCard(showImage: showImage, showButton: showButton)
}
}
.padding(.horizontal, 16)
}
}
// MARK: -
struct SmartRefreshView<Content: View>: View {
@State private var isRefreshing = false
let onRefresh: () async -> Void
let content: () -> Content
init(
onRefresh: @escaping () async -> Void,
@ViewBuilder content: @escaping () -> Content
) {
self.onRefresh = onRefresh
self.content = content
}
var body: some View {
ScrollView {
content()
}
.refreshable {
await onRefresh()
}
}
}
// MARK: -
struct LoadMoreView: View {
@State private var isLoading = false
let onLoadMore: () async -> Void
let hasMore: Bool
init(hasMore: Bool = true, onLoadMore: @escaping () async -> Void) {
self.hasMore = hasMore
self.onLoadMore = onLoadMore
}
var body: some View {
HStack {
Spacer()
if hasMore {
if isLoading {
HStack(spacing: 8) {
ProgressView()
.scaleEffect(0.8)
Text("加载中...")
.font(.caption)
.foregroundColor(Color.theme.secondaryText)
}
} else {
Button("加载更多") {
Task {
isLoading = true
await onLoadMore()
isLoading = false
}
}
.font(.caption)
.foregroundColor(Color.theme.accent)
}
} else {
Text("没有更多内容了")
.font(.caption)
.foregroundColor(Color.theme.tertiaryText)
}
Spacer()
}
.padding(.vertical, 16)
}
}
// MARK: -
#Preview("骨架屏组件") {
VStack(spacing: 20) {
SkeletonCard()
SkeletonListItem()
SkeletonText()
}
.padding()
.background(Color.theme.background)
}
@@ -1,55 +0,0 @@
//
// LoadingOverlay.swift
// EmotionMuseum
//
// Created by on 2025/7/5.
//
import SwiftUI
struct LoadingOverlay: View {
let message: String
@State private var rotationAngle: Double = 0
var body: some View {
ZStack {
//
Color.black
.opacity(0.3)
.ignoresSafeArea()
//
VStack(spacing: 20) {
//
Image(systemName: "brain.head.profile")
.font(.system(size: 40))
.foregroundColor(Color("AccentColor"))
.rotationEffect(.degrees(rotationAngle))
.onAppear {
withAnimation(.linear(duration: 2).repeatForever(autoreverses: false)) {
rotationAngle = 360
}
}
//
Text(message)
.font(.subheadline)
.foregroundColor(Color("PrimaryText"))
.multilineTextAlignment(.center)
}
.padding(30)
.background(
RoundedRectangle(cornerRadius: 16)
.fill(Color("CardBackground"))
.shadow(color: .black.opacity(0.1), radius: 10, x: 0, y: 5)
)
}
.transition(.opacity)
.zIndex(999) //
}
}
#Preview {
LoadingOverlay(message: "正在加载数据...")
.background(Color.gray.opacity(0.3))
}
@@ -1,232 +0,0 @@
import SwiftUI
// import AMapFoundationKit // CocoaPods
// import MAMapKit // CocoaPods
import UIKit
import MapKit
import CoreLocation
///
/// @Author huazhongmin
/// @Time 2024-03-24
/// @Description 使
struct MapView: View {
@Binding var shouldMoveToUserLocation: Bool
init(shouldMoveToUserLocation: Binding<Bool> = .constant(false)) {
self._shouldMoveToUserLocation = shouldMoveToUserLocation
}
var body: some View {
MapViewRepresentable(shouldMoveToUserLocation: $shouldMoveToUserLocation)
.edgesIgnoringSafeArea(.all)
}
}
/// SwiftUI
struct MapViewRepresentable: UIViewRepresentable {
@Binding var shouldMoveToUserLocation: Bool
//
let defaultLocation = CLLocationCoordinate2D(
latitude: 39.908823,
longitude: 116.397470
)
//
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
//
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView(frame: .zero)
mapView.delegate = context.coordinator
//
mapView.showsUserLocation = true
mapView.userTrackingMode = .none //
//
mapView.mapType = .standard
mapView.showsCompass = true
mapView.showsScale = true
mapView.showsTraffic = false
//
mapView.isZoomEnabled = true
mapView.isScrollEnabled = true
mapView.isRotateEnabled = true
mapView.isPitchEnabled = true
//
let initialRegion = MKCoordinateRegion(
center: defaultLocation,
span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
)
mapView.setRegion(initialRegion, animated: false)
// mapViewcoordinator
context.coordinator.mapView = mapView
context.coordinator.startLocationUpdates()
return mapView
}
//
func updateUIView(_ mapView: MKMapView, context: Context) {
//
context.coordinator.checkLocationPermission()
//
if shouldMoveToUserLocation {
context.coordinator.moveToUserLocation()
//
DispatchQueue.main.async {
shouldMoveToUserLocation = false
}
}
}
//
class Coordinator: NSObject, MKMapViewDelegate, CLLocationManagerDelegate {
var parent: MapViewRepresentable
var mapView: MKMapView?
var locationManager: CLLocationManager
var hasInitialLocationSet = false
init(_ parent: MapViewRepresentable) {
self.parent = parent
self.locationManager = CLLocationManager()
super.init()
//
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = 10 // 10
}
//
func startLocationUpdates() {
checkLocationPermission()
}
//
func checkLocationPermission() {
guard CLLocationManager.locationServicesEnabled() else {
print("定位服务未启用")
return
}
let authorizationStatus = locationManager.authorizationStatus
switch authorizationStatus {
case .notDetermined:
// 使
locationManager.requestWhenInUseAuthorization()
case .authorizedWhenInUse, .authorizedAlways:
//
locationManager.startUpdatingLocation()
case .denied, .restricted:
//
print("定位权限被拒绝,显示默认位置")
setDefaultLocation()
@unknown default:
print("未知的定位权限状态")
setDefaultLocation()
}
}
//
func setDefaultLocation() {
DispatchQueue.main.async {
guard let mapView = self.mapView else { return }
let region = MKCoordinateRegion(
center: self.parent.defaultLocation,
span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
)
mapView.setRegion(region, animated: true)
}
}
// MARK: - CLLocationManagerDelegate
//
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
checkLocationPermission()
}
//
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
//
if !hasInitialLocationSet {
DispatchQueue.main.async {
guard let mapView = self.mapView else { return }
let userRegion = MKCoordinateRegion(
center: location.coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
)
mapView.setRegion(userRegion, animated: true)
self.hasInitialLocationSet = true
print("已定位到用户位置: \(location.coordinate)")
}
//
locationManager.stopUpdatingLocation()
}
}
//
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("定位失败: \(error.localizedDescription)")
//
if !hasInitialLocationSet {
setDefaultLocation()
hasInitialLocationSet = true
}
}
// MARK: - MKMapViewDelegate
//
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
//
//
}
//
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
//
}
//
func moveToUserLocation() {
guard let mapView = self.mapView,
let userLocation = mapView.userLocation.location else {
//
checkLocationPermission()
return
}
let userRegion = MKCoordinateRegion(
center: userLocation.coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
)
mapView.setRegion(userRegion, animated: true)
}
}
}
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView()
}
}
@@ -1,662 +0,0 @@
//
// RecordView.swift
// EmotionMuseum
//
// Created by on 2025/6/13.
//
import SwiftUI
// MARK: -
struct RecordView: View {
@EnvironmentObject var navigationManager: NavigationManager
@EnvironmentObject var themeManager: ThemeManager
@EnvironmentObject var mockData: MockDataManager
@StateObject private var aiService = MockAIService()
@State private var selectedDate = Date()
@State private var inputText = ""
@State private var showingMoodPicker = false
@State private var selectedMood = ""
@State private var loadingState: LoadingState = .idle
@State private var isInitialLoading = true
var body: some View {
LoadingStateView(loadingState: isInitialLoading ? .loading : .loaded) {
VStack(spacing: 0) {
//
ScrollView {
LazyVStack(spacing: 16) {
//
topNavigationBar
.padding(.horizontal, 16)
.padding(.top, 8)
.transition(.move(edge: .top).combined(with: .opacity))
//
emotionCalendar
.padding(.horizontal, 16)
.transition(.scale(scale: 0.8).combined(with: .opacity))
// AI
aiAvatarSection
.padding(.horizontal, 16)
.transition(.scale(scale: 0.9).combined(with: .opacity))
//
chatArea
.padding(.horizontal, 16)
.transition(.opacity.combined(with: .slide))
}
.padding(.bottom, 10) //
}
.refreshable {
await simulateRefresh()
}
//
inputArea
.background(Color.theme.cardBackground)
.shadow(color: .black.opacity(0.1), radius: 8, y: -4)
.transition(.move(edge: .bottom).combined(with: .opacity))
}
} loadingView: {
AnyView(recordViewSkeleton)
}
.background(Color.theme.background)
.environmentObject(themeManager)
.preferredColorScheme(themeManager.systemFollowsDeviceTheme ? nil : (themeManager.isDarkMode ? .dark : .light))
.ignoresSafeArea(.keyboard, edges: .bottom) //
.sheet(isPresented: $showingMoodPicker) {
MoodPickerView(
selectedDate: selectedDate,
selectedMood: $selectedMood,
isPresented: $showingMoodPicker
)
}
.onAppear {
simulateInitialLoading()
}
}
// MARK: -
private var topNavigationBar: some View {
HStack {
// -
Button(action: { navigationManager.showChatHistory() }) {
Image(systemName: "bubble.left.and.bubble.right.fill")
.font(.title3)
.foregroundColor(.blue)
}
Spacer()
// -
Text("情绪记录")
.font(.title2)
.fontWeight(.semibold)
.foregroundColor(.primary)
Spacer()
// -
Button(action: { navigationManager.navigateToSettings() }) {
Image(systemName: "gearshape.fill")
.font(.title3)
.foregroundColor(.gray)
}
}
}
// MARK: -
private var emotionCalendar: some View {
VStack(spacing: 12) {
HStack {
Text(selectedDate.formatted(.dateTime.month(.wide)))
.font(.headline)
.foregroundColor(.primary)
Spacer()
Button(action: { showingMoodPicker = true }) {
HStack(spacing: 4) {
Text(selectedMood.isEmpty ? "记录心情" : selectedMood)
.font(.caption)
Image(systemName: "plus.circle")
.font(.caption)
}
.foregroundColor(.blue)
}
}
// 7
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
ForEach(-3...3, id: \.self) { dayOffset in
let date = Calendar.current.date(byAdding: .day, value: dayOffset, to: selectedDate) ?? selectedDate
let isToday = Calendar.current.isDate(date, inSameDayAs: Date())
let isSelected = Calendar.current.isDate(date, inSameDayAs: selectedDate)
VStack(spacing: 4) {
Text(DateFormatter.weekdayShort.string(from: date))
.font(.caption2)
.foregroundColor(.secondary)
Button(action: { selectedDate = date }) {
VStack(spacing: 2) {
Text("\(Calendar.current.component(.day, from: date))")
.font(.system(size: 16, weight: .medium))
.foregroundColor(isSelected ? .white : .primary)
//
Circle()
.fill(emotionColorForDate(date))
.frame(width: 6, height: 6)
.opacity(hasEmotionRecord(for: date) ? 1 : 0)
}
.frame(width: 40, height: 44)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(isSelected ? Color.blue : Color.clear)
)
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(isToday ? Color.blue : Color.clear, lineWidth: 1)
)
}
}
}
}
.padding(.horizontal, 16)
}
}
.padding()
.background(Color.theme.cardBackground)
.cornerRadius(16)
}
// MARK: - AI
private var aiAvatarSection: some View {
HStack {
Spacer()
// AI -
ZStack {
//
Circle()
.fill(
LinearGradient(
colors: [Color.blue.opacity(0.1), Color.purple.opacity(0.1)],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.frame(width: 80, height: 80)
// AI
Image(systemName: "brain.head.profile")
.font(.system(size: 35))
.foregroundColor(.blue)
.scaleEffect(aiService.isLoading ? 1.1 : 1.0)
.animation(.easeInOut(duration: 1).repeatForever(), value: aiService.isLoading)
}
Spacer()
}
.padding(.vertical, 10)
}
// MARK: -
private var chatArea: some View {
VStack(spacing: 0) {
if mockData.conversations.isEmpty {
//
defaultChatContent
} else {
//
chatMessagesList
}
}
.frame(minHeight: 300) //
}
// MARK: -
private var defaultChatContent: some View {
VStack(spacing: 20) {
// AI
HStack {
// AI
Circle()
.fill(Color.blue.opacity(0.1))
.frame(width: 32, height: 32)
.overlay(
Image(systemName: "brain.head.profile")
.font(.system(size: 16))
.foregroundColor(.blue)
)
//
VStack(alignment: .leading, spacing: 8) {
Text("你好!我是你的情绪陪伴师")
.font(.system(size: 16, weight: .medium))
.foregroundColor(Color.theme.primaryText)
Text(getGreetingText())
.font(.system(size: 14))
.foregroundColor(Color.theme.secondaryText)
}
.padding(.horizontal, 16)
.padding(.vertical, 12)
.background(
RoundedRectangle(cornerRadius: 18)
.fill(Color.theme.surfaceBackground)
.shadow(color: Color.black.opacity(0.05), radius: 2, x: 0, y: 1)
)
Spacer()
}
.padding(.horizontal, 4)
//
HStack {
Spacer()
Text("你可以这样开始对话")
.font(.caption)
.foregroundColor(Color.theme.tertiaryText)
Spacer()
}
.padding(.top, 10)
//
chatQuickReplyCards
}
.padding(.vertical, 20)
}
// MARK: -
private var chatMessagesList: some View {
LazyVStack(spacing: 12) {
ForEach(mockData.conversations.prefix(3), id: \.id) { conversation in
ConversationPreviewCard(conversation: conversation) {
navigationManager.navigateToChat(conversation: conversation)
}
}
}
.padding(.vertical, 10)
}
// MARK: -
private var chatQuickReplyCards: some View {
LazyVGrid(columns: [
GridItem(.flexible(), spacing: 8),
GridItem(.flexible(), spacing: 8)
], spacing: 12) {
ForEach(quickReplies, id: \.self) { reply in
Button(action: { sendQuickReply(reply) }) {
Text(reply)
.font(.system(size: 14, weight: .medium))
.foregroundColor(Color("AccentColor"))
.multilineTextAlignment(.center)
.lineLimit(2)
.padding(.horizontal, 12)
.padding(.vertical, 14)
.frame(maxWidth: .infinity)
.frame(minHeight: 70)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(Color.theme.cardBackground)
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(Color("AccentColor").opacity(0.3), lineWidth: 1)
)
.shadow(
color: Color.black.opacity(0.05),
radius: 3,
x: 0,
y: 2
)
)
}
.buttonStyle(PlainButtonStyle())
.scaleEffect(1.0)
.animation(.spring(response: 0.3, dampingFraction: 0.8), value: false)
}
}
}
private let quickReplies = [
"我今天感觉有点焦虑",
"想和你聊聊最近的压力",
"今天发生了一些开心的事",
"我需要一些建议"
]
// MARK: -
private var inputArea: some View {
VStack(spacing: 0) {
// 线
LinearGradient(
colors: [Color.theme.divider.opacity(0), Color.theme.divider, Color.theme.divider.opacity(0)],
startPoint: .leading,
endPoint: .trailing
)
.frame(height: 1)
//
HStack(spacing: 12) {
//
Button(action: { }) {
Image(systemName: "photo.circle.fill")
.font(.title2)
.foregroundColor(Color.theme.secondaryText)
}
//
HStack(spacing: 8) {
TextField("说说你的感受...", text: $inputText)
.textFieldStyle(PlainTextFieldStyle())
.foregroundColor(Color.theme.primaryText)
.padding(.vertical, 12)
.padding(.leading, 16)
//
Button(action: { }) {
Image(systemName: "mic.circle.fill")
.font(.title2)
.foregroundColor(Color("AccentColor"))
}
.padding(.trailing, 8)
}
.background(
RoundedRectangle(cornerRadius: 24)
.fill(Color.theme.surfaceBackground)
.shadow(
color: Color.black.opacity(0.05),
radius: 2,
x: 0,
y: 1
)
)
//
Button(action: sendMessage) {
Image(systemName: inputText.isEmpty ? "arrow.up.circle" : "arrow.up.circle.fill")
.font(.title2)
.foregroundColor(inputText.isEmpty ? Color.theme.secondaryText : Color("AccentColor"))
.scaleEffect(inputText.isEmpty ? 0.9 : 1.0)
.animation(.spring(response: 0.3, dampingFraction: 0.8), value: inputText.isEmpty)
}
.disabled(inputText.isEmpty)
}
.padding(.horizontal, 16)
.padding(.top, 12)
.padding(.bottom, 12) //
.background(
Color.theme.cardBackground
.overlay(
//
LinearGradient(
colors: [
Color.white.opacity(themeManager.isDarkMode ? 0.05 : 0.4),
Color.clear
],
startPoint: .top,
endPoint: .bottom
)
.frame(height: 1),
alignment: .top
)
)
// AI
if aiService.isLoading {
HStack(spacing: 4) {
ProgressView()
.scaleEffect(0.7)
.tint(Color("AccentColor"))
Text("AI思考中...")
.font(.caption2)
.foregroundColor(Color.theme.secondaryText)
}
.padding(.horizontal, 16)
.padding(.bottom, 12)
.background(Color.theme.cardBackground)
}
}
}
// MARK: -
private func getGreetingText() -> String {
let hour = Calendar.current.component(.hour, from: Date())
switch hour {
case 5..<12:
return "早上好!新的一天,新的开始。今天感觉怎么样?"
case 12..<17:
return "下午好!工作辛苦了,有什么想聊的吗?"
case 17..<22:
return "晚上好!一天结束了,让我们聊聊今天的感受吧。"
default:
return "夜深了,如果睡不着,我可以陪你聊聊。"
}
}
private func emotionColorForDate(_ date: Date) -> Color {
//
let day = Calendar.current.component(.day, from: date)
switch day % 6 {
case 0: return .red
case 1: return .blue
case 2: return .green
case 3: return .yellow
case 4: return .purple
default: return .orange
}
}
private func hasEmotionRecord(for date: Date) -> Bool {
//
let day = Calendar.current.component(.day, from: date)
return day % 3 != 0
}
private func sendQuickReply(_ reply: String) {
inputText = reply
sendMessage()
}
private func sendMessage() {
guard !inputText.isEmpty else { return }
let messageContent = inputText
inputText = ""
// 使
navigationManager.sendMessage(messageContent)
//
if navigationManager.currentChatConversation == nil {
navigationManager.navigateToChat()
}
}
private func loadConversations() {
//
// mockData.conversations = [] //
}
private func simulateInitialLoading() {
loadingState = .loading
//
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
withAnimation(.easeOut(duration: 0.6)) {
isInitialLoading = false
loadingState = .loaded
}
loadConversations()
}
}
private func simulateRefresh() async {
//
try? await Task.sleep(nanoseconds: 1_000_000_000)
await MainActor.run {
loadConversations()
}
}
// MARK: -
private var recordViewSkeleton: some View {
VStack(spacing: 0) {
//
HStack {
SkeletonView(width: 24, height: 24, cornerRadius: 12)
Spacer()
SkeletonView(width: 100, height: 20, cornerRadius: 6)
Spacer()
SkeletonView(width: 24, height: 24, cornerRadius: 12)
}
.padding(.horizontal, 16)
.padding(.top, 8)
//
VStack(spacing: 12) {
HStack {
SkeletonView(width: 80, height: 20, cornerRadius: 6)
Spacer()
SkeletonView(width: 60, height: 16, cornerRadius: 4)
}
HStack(spacing: 16) {
ForEach(0..<7, id: \.self) { _ in
VStack(spacing: 4) {
SkeletonView(width: 20, height: 12, cornerRadius: 3)
SkeletonView(width: 40, height: 44, cornerRadius: 12)
}
}
}
}
.padding()
.background(Color.theme.cardBackground)
.cornerRadius(16)
.padding(.horizontal, 16)
.padding(.vertical, 8)
Spacer()
// AI
VStack(spacing: 24) {
SkeletonView(width: 200, height: 200, cornerRadius: 100)
VStack(spacing: 8) {
SkeletonView(width: 200, height: 20, cornerRadius: 6)
SkeletonView(width: 250, height: 16, cornerRadius: 4)
SkeletonView(width: 180, height: 16, cornerRadius: 4)
}
VStack(spacing: 8) {
SkeletonView(width: 120, height: 12, cornerRadius: 3)
ForEach(0..<3, id: \.self) { _ in
SkeletonView(width: 160, height: 32, cornerRadius: 16)
}
}
}
.padding(.horizontal, 24)
Spacer()
//
VStack(spacing: 12) {
HStack(spacing: 12) {
SkeletonView(height: 48, cornerRadius: 24)
SkeletonView(width: 48, height: 48, cornerRadius: 24)
}
HStack(spacing: 24) {
ForEach(0..<2, id: \.self) { _ in
VStack(spacing: 4) {
SkeletonView(width: 24, height: 24, cornerRadius: 12)
SkeletonView(width: 30, height: 12, cornerRadius: 3)
}
}
Spacer()
}
}
.padding(.horizontal, 16)
.padding(.top, 16)
.padding(.bottom, 24)
.background(Color.theme.cardBackground)
}
.background(Color.theme.background)
}
}
// MARK: -
//
struct MoodPickerView: View {
let selectedDate: Date
@Binding var selectedMood: String
@Binding var isPresented: Bool
let moods = [
("😊", "开心"), ("😢", "难过"), ("😡", "愤怒"),
("😰", "焦虑"), ("😌", "平静"), ("🤔", "思考")
]
var body: some View {
NavigationView {
VStack(spacing: 24) {
Text("选择今日心情")
.font(.title2)
.fontWeight(.semibold)
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 3), spacing: 16) {
ForEach(moods, id: \.0) { emoji, name in
Button(action: {
selectedMood = emoji
isPresented = false
}) {
VStack(spacing: 8) {
Text(emoji)
.font(.system(size: 40))
Text(name)
.font(.caption)
.foregroundColor(.primary)
}
.frame(width: 80, height: 80)
.background(Color(.systemGray6))
.cornerRadius(16)
}
}
}
.padding()
Spacer()
}
.navigationTitle("心情记录")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("取消") { isPresented = false }
}
}
}
}
}
// MARK: -
extension DateFormatter {
static let weekdayShort: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "E"
return formatter
}()
}
@@ -1,875 +0,0 @@
//
// SupportViews.swift
// EmotionMuseum
//
// Created by huazhongmin on 2024/01/01.
//
import SwiftUI
import MapKit
// MARK: -
struct AstroAnalysisView: View {
@State private var selectedDate = Date()
@State private var selectedTime = Date()
@State private var birthLocation = ""
var body: some View {
NavigationView {
ScrollView {
VStack(spacing: 24) {
Text("通过占星学了解你的内在特质")
.font(.headline)
.multilineTextAlignment(.center)
.padding()
VStack(spacing: 16) {
DatePicker("出生日期", selection: $selectedDate, displayedComponents: .date)
DatePicker("出生时间", selection: $selectedTime, displayedComponents: .hourAndMinute)
TextField("出生地点", text: $birthLocation)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding()
Button("生成星盘分析") {
//
}
.buttonStyle(.borderedProminent)
.padding()
}
}
.navigationTitle("占星分析")
}
}
}
// MARK: -
struct LocationMarkerCard: View {
let location: LocationPin
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Image(systemName: location.category.icon)
.foregroundColor(location.emotion.color)
Text(location.name)
.font(.headline)
Spacer()
Text(location.emotion.emoji)
.font(.title2)
}
Text(location.description)
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(2)
}
.padding()
.background(Color(.systemBackground))
.cornerRadius(12)
.shadow(radius: 2)
}
}
// MARK: -
struct RecommendedLocationCard: View {
let location: LocationPin
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text(location.name)
.font(.headline)
.fontWeight(.semibold)
Text(location.category.rawValue)
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
VStack(spacing: 4) {
Text(location.emotion.emoji)
.font(.title)
Text(location.emotion.displayName)
.font(.caption2)
.foregroundColor(location.emotion.color)
}
}
Text(location.description)
.font(.subheadline)
.foregroundColor(.secondary)
.lineLimit(2)
HStack {
Label("\(location.visitCount)", systemImage: "person.2")
.font(.caption)
.foregroundColor(.secondary)
Spacer()
if !location.tags.isEmpty {
Text("#\(location.tags.first ?? "")")
.font(.caption)
.foregroundColor(.blue)
.padding(.horizontal, 8)
.padding(.vertical, 2)
.background(Color.blue.opacity(0.1))
.cornerRadius(4)
}
}
}
.padding()
.background(Color(.systemBackground))
.cornerRadius(16)
.shadow(color: .black.opacity(0.05), radius: 8, y: 2)
}
}
// MARK: -
struct CommunityPostCard: View {
let post: CommunityPost
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Circle()
.fill(Color.blue)
.frame(width: 40, height: 40)
.overlay(
Text(String(post.authorName.prefix(1)))
.foregroundColor(.white)
.fontWeight(.semibold)
)
VStack(alignment: .leading, spacing: 2) {
Text(post.authorName)
.font(.subheadline)
.fontWeight(.medium)
Text(DateFormatter.shortRelative.string(from: post.createdAt))
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
Text("💭")
.font(.title2)
}
Text(post.content)
.font(.body)
.lineLimit(3)
if !post.tags.isEmpty {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
ForEach(post.tags, id: \.self) { tag in
Text("#\(tag)")
.font(.caption)
.foregroundColor(.blue)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(Color.blue.opacity(0.1))
.cornerRadius(8)
}
}
.padding(.horizontal, 4)
}
}
HStack {
Button(action: {}) {
HStack(spacing: 4) {
Image(systemName: post.isLikedByCurrentUser ? "heart.fill" : "heart")
.foregroundColor(post.isLikedByCurrentUser ? .red : .gray)
Text("\(post.likes)")
.font(.caption)
.foregroundColor(.secondary)
}
}
Button(action: {}) {
HStack(spacing: 4) {
Image(systemName: "message")
.foregroundColor(.gray)
Text("\(post.comments.count)")
.font(.caption)
.foregroundColor(.secondary)
}
}
Spacer()
Button(action: {}) {
Image(systemName: "square.and.arrow.up")
.foregroundColor(.gray)
}
}
}
.padding()
.background(Color(.systemBackground))
.cornerRadius(16)
.shadow(color: .black.opacity(0.05), radius: 8, y: 2)
}
}
// MARK: -
struct AllLocationsView: View {
@EnvironmentObject var dataManager: MockDataManager
@State private var searchText = ""
@State private var selectedCategory: LocationCategory?
@State private var sortOption: SortOption = .recent
enum SortOption: String, CaseIterable {
case recent = "最近访问"
case popular = "最受欢迎"
case nearby = "距离最近"
case alphabetical = "按名称"
}
var filteredLocations: [LocationPin] {
let filtered = dataManager.locationPins.filter { location in
let matchesSearch = searchText.isEmpty ||
location.name.localizedCaseInsensitiveContains(searchText) ||
location.description.localizedCaseInsensitiveContains(searchText) ||
location.tags.contains { $0.localizedCaseInsensitiveContains(searchText) }
let matchesCategory = selectedCategory == nil || location.category == selectedCategory
return matchesSearch && matchesCategory
}
switch sortOption {
case .recent:
return filtered.sorted { ($0.lastVisitAt ?? Date.distantPast) > ($1.lastVisitAt ?? Date.distantPast) }
case .popular:
return filtered.sorted { $0.visitCount > $1.visitCount }
case .nearby:
return filtered //
case .alphabetical:
return filtered.sorted { $0.name < $1.name }
}
}
var body: some View {
NavigationView {
VStack(spacing: 0) {
//
SearchBar(text: $searchText)
.padding()
//
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12) {
//
Button(action: { selectedCategory = nil }) {
Text("全部")
.font(.caption)
.foregroundColor(selectedCategory == nil ? .white : .primary)
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(selectedCategory == nil ? Color.blue : Color(.systemGray6))
.cornerRadius(16)
}
ForEach(LocationCategory.allCases, id: \.self) { category in
Button(action: { selectedCategory = category }) {
Text(category.rawValue)
.font(.caption)
.foregroundColor(selectedCategory == category ? .white : .primary)
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(selectedCategory == category ? Color.blue : Color(.systemGray6))
.cornerRadius(16)
}
}
}
.padding(.horizontal)
}
.padding(.bottom)
//
Picker("排序", selection: $sortOption) {
ForEach(SortOption.allCases, id: \.self) { option in
Text(option.rawValue).tag(option)
}
}
.pickerStyle(SegmentedPickerStyle())
.padding(.horizontal)
//
ScrollView {
LazyVStack(spacing: 16) {
ForEach(filteredLocations) { location in
AllLocationCard(location: location)
}
}
.padding()
}
}
.navigationTitle("所有地点")
}
}
}
// MARK: -
struct SearchBar: View {
@Binding var text: String
var body: some View {
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
TextField("搜索地点、标签...", text: $text)
.textFieldStyle(PlainTextFieldStyle())
if !text.isEmpty {
Button(action: { text = "" }) {
Image(systemName: "xmark.circle.fill")
.foregroundColor(.gray)
}
}
}
.padding(.horizontal, 12)
.padding(.vertical, 8)
.background(Color(.systemGray6))
.cornerRadius(10)
}
}
// MARK: -
struct AllLocationCard: View {
let location: LocationPin
var body: some View {
HStack(spacing: 16) {
//
VStack(spacing: 8) {
Image(systemName: location.category.icon)
.font(.title2)
.foregroundColor(location.emotion.color)
.frame(width: 40, height: 40)
.background(location.emotion.color.opacity(0.2))
.cornerRadius(12)
Text(location.emotion.emoji)
.font(.title3)
}
//
VStack(alignment: .leading, spacing: 4) {
Text(location.name)
.font(.headline)
.fontWeight(.semibold)
Text(location.description)
.font(.subheadline)
.foregroundColor(.secondary)
.lineLimit(2)
HStack {
Label("\(location.visitCount)次访问", systemImage: "clock")
.font(.caption)
.foregroundColor(.secondary)
Spacer()
if let lastVisit = location.lastVisitAt {
Text(DateFormatter.shortRelative.string(from: lastVisit))
.font(.caption)
.foregroundColor(.secondary)
}
}
}
Spacer()
//
VStack(spacing: 8) {
Button(action: {}) {
Image(systemName: location.isBookmarked ? "bookmark.fill" : "bookmark")
.foregroundColor(location.isBookmarked ? .blue : .gray)
}
Button(action: {}) {
Image(systemName: "square.and.arrow.up")
.foregroundColor(.gray)
}
}
}
.padding()
.background(Color(.systemBackground))
.cornerRadius(16)
.shadow(color: .black.opacity(0.05), radius: 8, y: 2)
}
}
// MARK: -
struct CreatePostView: View {
let selectedLocation: LocationPin?
@Environment(\.dismiss) private var dismiss
@State private var postContent = ""
@State private var selectedEmotion: EmotionType = .neutral
@State private var selectedTags: Set<String> = []
let availableTags = ["推荐", "美食", "风景", "心情", "感悟", "治愈", "安静", "热闹"]
var body: some View {
NavigationView {
ScrollView {
VStack(spacing: 20) {
//
VStack(alignment: .leading, spacing: 8) {
Text("分享你的感受")
.font(.headline)
TextEditor(text: $postContent)
.frame(height: 120)
.padding(8)
.background(Color(.systemGray6))
.cornerRadius(12)
}
//
VStack(alignment: .leading, spacing: 8) {
Text("当前情绪")
.font(.headline)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12) {
ForEach(EmotionType.allCases, id: \.self) { emotion in
Button(action: { selectedEmotion = emotion }) {
VStack(spacing: 4) {
Text(emotion.emoji)
.font(.title2)
Text(emotion.rawValue)
.font(.caption)
.foregroundColor(.primary)
}
.padding(8)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(selectedEmotion == emotion ? emotion.color.opacity(0.3) : Color(.systemGray6))
)
}
}
}
.padding(.horizontal, 4)
}
}
//
VStack(alignment: .leading, spacing: 8) {
Text("添加标签")
.font(.headline)
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 4), spacing: 8) {
ForEach(availableTags, id: \.self) { tag in
Button(action: {
if selectedTags.contains(tag) {
selectedTags.remove(tag)
} else {
selectedTags.insert(tag)
}
}) {
Text("#\(tag)")
.font(.caption)
.foregroundColor(selectedTags.contains(tag) ? .white : .primary)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(selectedTags.contains(tag) ? Color.blue : Color(.systemGray6))
)
}
}
}
}
//
if let location = selectedLocation {
VStack(alignment: .leading, spacing: 8) {
Text("位置")
.font(.headline)
HStack(spacing: 12) {
Image(systemName: location.category.icon)
.foregroundColor(.blue)
VStack(alignment: .leading, spacing: 2) {
Text(location.name)
.font(.subheadline)
.fontWeight(.medium)
if let address = location.address {
Text(address)
.font(.caption)
.foregroundColor(.secondary)
}
}
Spacer()
}
.padding()
.background(Color(.systemGray6))
.cornerRadius(12)
}
}
}
.padding()
}
.navigationTitle("分享动态")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("取消") { dismiss() }
}
ToolbarItem(placement: .confirmationAction) {
Button("发布") {
//
dismiss()
}
.disabled(postContent.isEmpty)
}
}
}
}
}
// MARK: -
struct LocationDetailView: View {
let location: LocationPin
@Environment(\.dismiss) private var dismiss
@State private var showingShareView = false
var body: some View {
NavigationView {
ScrollView {
VStack(spacing: 24) {
//
VStack(spacing: 16) {
HStack(spacing: 20) {
Image(systemName: location.category.icon)
.font(.system(size: 48))
.foregroundColor(location.emotion.color)
.frame(width: 80, height: 80)
.background(location.emotion.color.opacity(0.2))
.cornerRadius(20)
VStack(spacing: 8) {
Text(location.emotion.emoji)
.font(.system(size: 60))
Text(location.emotion.displayName)
.font(.headline)
.foregroundColor(location.emotion.color)
}
}
Text(location.name)
.font(.title)
.fontWeight(.bold)
.multilineTextAlignment(.center)
Text(location.description)
.font(.body)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal)
}
.padding(.vertical, 20)
.frame(maxWidth: .infinity)
.background(
LinearGradient(
colors: [location.emotion.color.opacity(0.1), location.emotion.color.opacity(0.05)],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.cornerRadius(20)
//
VStack(alignment: .leading, spacing: 12) {
Text("基本信息")
.font(.headline)
InfoRow(icon: "location", title: "地址", value: location.address ?? "未设置")
InfoRow(icon: "list.bullet", title: "类别", value: location.category.rawValue)
InfoRow(icon: "number", title: "访问次数", value: "\(location.visitCount)")
}
.padding()
.background(Color(.systemBackground))
.cornerRadius(16)
.shadow(radius: 2)
//
VStack(spacing: 12) {
Button(action: { showingShareView = true }) {
HStack {
Image(systemName: "square.and.pencil")
Text("在这里分享心情")
}
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(location.emotion.color)
.cornerRadius(12)
}
HStack(spacing: 12) {
Button(action: {}) {
HStack {
Image(systemName: "bookmark")
Text("收藏")
}
.frame(maxWidth: .infinity)
.padding()
.background(Color(.systemGray6))
.cornerRadius(12)
}
Button(action: {}) {
HStack {
Image(systemName: "square.and.arrow.up")
Text("分享")
}
.frame(maxWidth: .infinity)
.padding()
.background(Color(.systemGray6))
.cornerRadius(12)
}
}
}
.padding(.horizontal)
}
.padding()
}
.navigationTitle("")
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden(true)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: { dismiss() }) {
HStack(spacing: 4) {
Image(systemName: "xmark.circle.fill")
.foregroundColor(.gray)
Text("关闭")
.foregroundColor(.primary)
}
}
}
}
}
.sheet(isPresented: $showingShareView) {
CreatePostView(selectedLocation: location)
}
}
}
// MARK: -
struct InfoRow: View {
let icon: String
let title: String
let value: String
var body: some View {
HStack {
Image(systemName: icon)
.foregroundColor(.blue)
.frame(width: 20)
Text(title)
.font(.subheadline)
.foregroundColor(.secondary)
Spacer()
Text(value)
.font(.subheadline)
.fontWeight(.medium)
}
}
}
// MARK: - 访
struct VisitRecord: Identifiable, Codable {
let id: UUID
let date: Date
let emotion: EmotionType
let notes: String
init(id: UUID = UUID(), date: Date, emotion: EmotionType, notes: String) {
self.id = id
self.date = date
self.emotion = emotion
self.notes = notes
}
}
struct VisitHistoryRow: View {
let visit: VisitRecord
let isLatest: Bool
var body: some View {
HStack(spacing: 12) {
VStack(spacing: 4) {
Text(visit.emotion.emoji)
.font(.title3)
if isLatest {
Circle()
.fill(Color.green)
.frame(width: 6, height: 6)
} else {
Circle()
.fill(Color.gray.opacity(0.3))
.frame(width: 4, height: 4)
}
}
VStack(alignment: .leading, spacing: 4) {
HStack {
Text(visit.emotion.displayName)
.font(.subheadline)
.fontWeight(.medium)
.foregroundColor(visit.emotion.color)
Spacer()
Text(DateFormatter.localizedDate.string(from: visit.date))
.font(.caption)
.foregroundColor(.secondary)
}
Text(visit.notes)
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(2)
}
}
.padding(.vertical, 4)
}
}
// MARK: -
extension DateFormatter {
static let localizedDate: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
return formatter
}()
static let shortRelative: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .none
return formatter
}()
}
// MARK: - 访
func generateVisitNote(for location: LocationPin, emotion: EmotionType) -> String {
let notes = [
"在这里度过了美好的时光",
"心情得到了很好的调节",
"这个地方让我感到平静",
"和朋友一起来的,很开心",
"独自一人,享受安静的时光",
"记录了这次特别的体验"
]
return notes.randomElement() ?? "记录了这次访问"
}
// MARK: -
struct GuidedSelectionView: View {
var body: some View {
VStack {
Text("引导选择")
.font(.title)
.padding()
Text("这里是引导用户进行选择的界面")
.foregroundColor(.secondary)
.padding()
Spacer()
}
}
}
struct AstroAnalysisInputView: View {
var body: some View {
VStack {
Text("占星分析输入")
.font(.title)
.padding()
Text("这里是占星分析的输入界面")
.foregroundColor(.secondary)
.padding()
Spacer()
}
}
}
// MARK: -
struct TopicDetailView: View {
let topic: GrowthTopic
var body: some View {
VStack {
Text(topic.title)
.font(.title)
.padding()
Text(topic.description)
.foregroundColor(.secondary)
.padding()
Spacer()
}
}
}
struct EmotionalInsightsView: View {
var body: some View {
VStack {
Text("情绪洞察")
.font(.title)
.padding()
Text("这里显示用户的情绪分析和洞察")
.foregroundColor(.secondary)
.padding()
Spacer()
}
}
}
struct ChatHistoryView: View {
var body: some View {
VStack {
Text("对话历史")
.font(.title)
.padding()
Text("这里显示与AI的对话历史")
.foregroundColor(.secondary)
.padding()
Spacer()
}
}
}
@@ -1,415 +0,0 @@
//
// ThemeAdapter.swift
// EmotionMuseum
//
// Created by on 2025/6/13.
//
import SwiftUI
// MARK: -
struct ThemedCard<Content: View>: View {
let content: () -> Content
let padding: CGFloat
let cornerRadius: CGFloat
let shadowEnabled: Bool
init(
padding: CGFloat = 16,
cornerRadius: CGFloat = 16,
shadowEnabled: Bool = true,
@ViewBuilder content: @escaping () -> Content
) {
self.padding = padding
self.cornerRadius = cornerRadius
self.shadowEnabled = shadowEnabled
self.content = content
}
var body: some View {
content()
.padding(padding)
.background(Color.theme.cardBackground)
.cornerRadius(cornerRadius)
.shadow(
color: shadowEnabled ? Color.black.opacity(0.05) : Color.clear,
radius: shadowEnabled ? 8 : 0,
x: 0,
y: shadowEnabled ? 2 : 0
)
}
}
// MARK: -
struct ThemedText: View {
let text: String
let style: TextStyle
let alignment: TextAlignment
enum TextStyle {
case title, headline, subheadline, body, caption
case primary, secondary, tertiary
var font: Font {
switch self {
case .title: return .title
case .headline: return .headline
case .subheadline: return .subheadline
case .body: return .body
case .caption: return .caption
case .primary, .secondary, .tertiary: return .body
}
}
var color: Color {
switch self {
case .title, .headline, .subheadline, .body, .primary:
return Color.theme.primaryText
case .secondary:
return Color.theme.secondaryText
case .tertiary, .caption:
return Color.theme.tertiaryText
}
}
}
init(_ text: String, style: TextStyle = .body, alignment: TextAlignment = .leading) {
self.text = text
self.style = style
self.alignment = alignment
}
var body: some View {
Text(text)
.font(style.font)
.foregroundColor(style.color)
.multilineTextAlignment(alignment)
}
}
// MARK: -
struct ThemedButton: View {
let title: String
let style: ButtonStyle
let size: ButtonSize
let action: () -> Void
enum ButtonStyle {
case primary, secondary, outline, text
var backgroundColor: Color {
switch self {
case .primary: return Color.theme.accent
case .secondary: return Color.theme.secondary
case .outline, .text: return Color.clear
}
}
var foregroundColor: Color {
switch self {
case .primary: return .white
case .secondary: return Color.theme.primaryText
case .outline: return Color.theme.accent
case .text: return Color.theme.accent
}
}
var borderColor: Color {
switch self {
case .outline: return Color.theme.accent
default: return Color.clear
}
}
}
enum ButtonSize {
case small, medium, large
var padding: EdgeInsets {
switch self {
case .small: return EdgeInsets(top: 8, leading: 12, bottom: 8, trailing: 12)
case .medium: return EdgeInsets(top: 12, leading: 16, bottom: 12, trailing: 16)
case .large: return EdgeInsets(top: 16, leading: 24, bottom: 16, trailing: 24)
}
}
var cornerRadius: CGFloat {
switch self {
case .small: return 8
case .medium: return 12
case .large: return 16
}
}
}
init(_ title: String, style: ButtonStyle = .primary, size: ButtonSize = .medium, action: @escaping () -> Void) {
self.title = title
self.style = style
self.size = size
self.action = action
}
var body: some View {
Button(action: action) {
Text(title)
.font(.subheadline)
.fontWeight(.medium)
.foregroundColor(style.foregroundColor)
.padding(size.padding)
.background(style.backgroundColor)
.overlay(
RoundedRectangle(cornerRadius: size.cornerRadius)
.stroke(style.borderColor, lineWidth: style == .outline ? 1 : 0)
)
.cornerRadius(size.cornerRadius)
}
}
}
// MARK: - 线
struct ThemedDivider: View {
let thickness: CGFloat
let color: Color?
init(thickness: CGFloat = 1, color: Color? = nil) {
self.thickness = thickness
self.color = color
}
var body: some View {
Rectangle()
.fill(color ?? Color.theme.divider)
.frame(height: thickness)
}
}
// MARK: -
struct ThemedProgressView: View {
let value: Double
let total: Double
let height: CGFloat
let backgroundColor: Color?
let foregroundColor: Color?
init(
value: Double,
total: Double = 1.0,
height: CGFloat = 8,
backgroundColor: Color? = nil,
foregroundColor: Color? = nil
) {
self.value = value
self.total = total
self.height = height
self.backgroundColor = backgroundColor
self.foregroundColor = foregroundColor
}
var body: some View {
ProgressView(value: value, total: total)
.progressViewStyle(
ThemedLinearProgressViewStyle(
height: height,
backgroundColor: backgroundColor ?? Color.theme.skeleton,
foregroundColor: foregroundColor ?? Color.theme.accent
)
)
}
}
struct ThemedLinearProgressViewStyle: ProgressViewStyle {
let height: CGFloat
let backgroundColor: Color
let foregroundColor: Color
func makeBody(configuration: Configuration) -> some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
Rectangle()
.fill(backgroundColor)
.frame(height: height)
.cornerRadius(height / 2)
Rectangle()
.fill(foregroundColor)
.frame(
width: geometry.size.width * CGFloat(configuration.fractionCompleted ?? 0),
height: height
)
.cornerRadius(height / 2)
}
}
.frame(height: height)
}
}
// MARK: -
struct ThemedTextField: View {
let placeholder: String
@Binding var text: String
let style: TextFieldStyle
enum TextFieldStyle {
case standard, rounded, outline
var backgroundColor: Color {
switch self {
case .standard: return Color.clear
case .rounded: return Color.theme.surfaceBackground
case .outline: return Color.theme.background
}
}
var borderColor: Color {
switch self {
case .outline: return Color.theme.border
default: return Color.clear
}
}
var cornerRadius: CGFloat {
switch self {
case .rounded: return 12
case .outline: return 8
default: return 0
}
}
}
init(_ placeholder: String, text: Binding<String>, style: TextFieldStyle = .standard) {
self.placeholder = placeholder
self._text = text
self.style = style
}
var body: some View {
TextField(placeholder, text: $text)
.padding(.horizontal, style == .standard ? 0 : 12)
.padding(.vertical, style == .standard ? 0 : 10)
.background(style.backgroundColor)
.foregroundColor(Color.theme.primaryText)
.overlay(
RoundedRectangle(cornerRadius: style.cornerRadius)
.stroke(style.borderColor, lineWidth: style == .outline ? 1 : 0)
)
.cornerRadius(style.cornerRadius)
}
}
// MARK: -
struct ThemedIcon: View {
let systemName: String
let style: IconStyle
let size: IconSize
enum IconStyle {
case primary, secondary, accent, custom(Color)
var color: Color {
switch self {
case .primary: return Color.theme.primaryText
case .secondary: return Color.theme.secondaryText
case .accent: return Color.theme.accent
case .custom(let color): return color
}
}
}
enum IconSize {
case small, medium, large, custom(CGFloat)
var font: Font {
switch self {
case .small: return .caption
case .medium: return .body
case .large: return .title2
case .custom(let size): return .system(size: size)
}
}
}
init(_ systemName: String, style: IconStyle = .primary, size: IconSize = .medium) {
self.systemName = systemName
self.style = style
self.size = size
}
var body: some View {
Image(systemName: systemName)
.font(size.font)
.foregroundColor(style.color)
}
}
// MARK: -
struct ThemedListRow<Content: View>: View {
let content: () -> Content
let showSeparator: Bool
let padding: EdgeInsets
init(
showSeparator: Bool = true,
padding: EdgeInsets = EdgeInsets(top: 12, leading: 16, bottom: 12, trailing: 16),
@ViewBuilder content: @escaping () -> Content
) {
self.showSeparator = showSeparator
self.padding = padding
self.content = content
}
var body: some View {
VStack(spacing: 0) {
content()
.padding(padding)
.background(Color.theme.cardBackground)
if showSeparator {
ThemedDivider()
.padding(.leading, padding.leading)
}
}
}
}
// MARK: -
extension View {
func themedCard(
padding: CGFloat = 16,
cornerRadius: CGFloat = 16,
shadowEnabled: Bool = true
) -> some View {
ThemedCard(
padding: padding,
cornerRadius: cornerRadius,
shadowEnabled: shadowEnabled
) {
self
}
}
func themedBackground() -> some View {
background(Color.theme.background)
}
func themedSurface() -> some View {
background(Color.theme.surfaceBackground)
}
}
// MARK: -
#Preview("主题组件") {
VStack(spacing: 20) {
ThemedText("主标题", style: .title)
ThemedText("副标题文本", style: .secondary)
ThemedButton("主要按钮") {}
ThemedButton("次要按钮", style: .secondary) {}
ThemedProgressView(value: 0.6)
.frame(height: 8)
ThemedTextField("请输入内容", text: .constant(""))
}
.padding()
.themedBackground()
}
@@ -1,367 +0,0 @@
//
// ThemeSettingsView.swift
// EmotionMuseum
//
// Created by on 2025/6/13.
//
import SwiftUI
// MARK: -
struct ThemeSettingsView: View {
@EnvironmentObject var themeManager: ThemeManager
@Environment(\.dismiss) var dismiss
var body: some View {
NavigationView {
List {
//
themeSelectionSection
//
previewSection
//
advancedSettingsSection
}
.listStyle(InsetGroupedListStyle())
.navigationTitle("主题设置")
.navigationBarTitleDisplayMode(.large)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("完成") {
dismiss()
}
.foregroundColor(Color.theme.accent)
}
}
}
.themedBackground()
.preferredColorScheme(themeManager.systemFollowsDeviceTheme ? nil : (themeManager.isDarkMode ? .dark : .light))
}
// MARK: -
private var themeSelectionSection: some View {
Section {
//
ThemedListRow {
HStack {
ThemedIcon("gear.circle.fill", style: .accent, size: .medium)
VStack(alignment: .leading, spacing: 2) {
ThemedText("跟随系统", style: .headline)
ThemedText("自动适应系统的深色模式设置", style: .secondary)
}
Spacer()
Toggle("", isOn: $themeManager.systemFollowsDeviceTheme)
.toggleStyle(SwitchToggleStyle(tint: Color.theme.accent))
}
}
if !themeManager.systemFollowsDeviceTheme {
//
ThemeOptionRow(
title: "浅色模式",
description: "明亮清新的视觉体验",
icon: "sun.max.fill",
isSelected: !themeManager.isDarkMode
) {
withAnimation(AnimationConfig.smooth) {
themeManager.isDarkMode = false
}
}
//
ThemeOptionRow(
title: "深色模式",
description: "舒适护眼的暗色调体验",
icon: "moon.fill",
isSelected: themeManager.isDarkMode
) {
withAnimation(AnimationConfig.smooth) {
themeManager.isDarkMode = true
}
}
}
} header: {
ThemedText("外观模式", style: .caption)
.foregroundColor(Color.theme.secondaryText)
}
}
// MARK: -
private var previewSection: some View {
Section {
VStack(spacing: 16) {
//
PreviewCard()
//
ColorPreviewGrid()
}
.padding(.vertical, 8)
} header: {
ThemedText("预览效果", style: .caption)
.foregroundColor(Color.theme.secondaryText)
}
}
// MARK: -
private var advancedSettingsSection: some View {
Section {
//
ThemedListRow {
Button(action: resetThemeSettings) {
HStack {
ThemedIcon("arrow.clockwise.circle.fill", style: .custom(.orange), size: .medium)
ThemedText("重置主题设置", style: .headline)
Spacer()
}
}
}
//
ThemedListRow(showSeparator: false) {
NavigationLink {
ThemeInfoView()
} label: {
HStack {
ThemedIcon("info.circle.fill", style: .accent, size: .medium)
ThemedText("关于主题", style: .headline)
Spacer()
ThemedIcon("chevron.right", style: .secondary, size: .small)
}
}
}
} header: {
ThemedText("高级选项", style: .caption)
.foregroundColor(Color.theme.secondaryText)
}
}
private func resetThemeSettings() {
withAnimation(AnimationConfig.smooth) {
themeManager.systemFollowsDeviceTheme = true
themeManager.isDarkMode = false
}
//
let impactFeedback = UIImpactFeedbackGenerator(style: .medium)
impactFeedback.impactOccurred()
}
}
// MARK: -
struct ThemeOptionRow: View {
let title: String
let description: String
let icon: String
let isSelected: Bool
let action: () -> Void
var body: some View {
ThemedListRow {
Button(action: action) {
HStack(spacing: 12) {
ThemedIcon(icon, style: .accent, size: .medium)
VStack(alignment: .leading, spacing: 2) {
ThemedText(title, style: .headline)
ThemedText(description, style: .secondary)
}
Spacer()
if isSelected {
ThemedIcon("checkmark.circle.fill", style: .custom(.green), size: .medium)
.transition(.scale.combined(with: .opacity))
}
}
}
.buttonStyle(PlainButtonStyle())
}
}
}
// MARK: -
struct PreviewCard: View {
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
ThemedText("示例卡片", style: .headline)
Spacer()
ThemedIcon("heart.fill", style: .custom(.red), size: .medium)
}
ThemedText("这是一个预览卡片,展示当前主题的效果。文本清晰度和对比度都经过精心调校。", style: .secondary)
HStack {
ThemedButton("主要按钮", size: .small) {}
ThemedButton("次要按钮", style: .secondary, size: .small) {}
Spacer()
}
ThemedProgressView(value: 0.6)
.frame(height: 6)
}
.themedCard()
}
}
// MARK: -
struct ColorPreviewGrid: View {
let colors: [(String, Color)] = [
("主色调", Color.theme.accent),
("成功", Color.theme.success),
("警告", Color.theme.warning),
("错误", Color.theme.error)
]
var body: some View {
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 4), spacing: 12) {
ForEach(colors, id: \.0) { name, color in
VStack(spacing: 6) {
Circle()
.fill(color)
.frame(width: 32, height: 32)
ThemedText(name, style: .caption)
.lineLimit(1)
}
}
}
.themedCard(padding: 12)
}
}
// MARK: -
struct ThemeInfoView: View {
@Environment(\.dismiss) var dismiss
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 24) {
//
VStack(alignment: .leading, spacing: 8) {
ThemedText("关于主题系统", style: .title)
ThemedText("智能适配,呵护双眼", style: .secondary)
}
//
VStack(alignment: .leading, spacing: 16) {
FeatureRow(
icon: "eye.fill",
title: "护眼设计",
description: "精心调校的颜色对比度,长时间使用不疲劳"
)
FeatureRow(
icon: "paintbrush.fill",
title: "精美配色",
description: "专业设计师打造的色彩方案,视觉体验更佳"
)
FeatureRow(
icon: "gear.badge.checkmark",
title: "智能适配",
description: "可跟随系统设置自动切换,也可手动调节"
)
FeatureRow(
icon: "moon.stars.fill",
title: "深色模式",
description: "夜间使用更舒适,有效减少蓝光刺激"
)
}
Spacer(minLength: 32)
//
VStack(spacing: 8) {
ThemedText("主题系统 v1.0", style: .secondary)
ThemedText("情绪博物馆团队制作", style: .tertiary)
}
.frame(maxWidth: .infinity)
}
.padding(.horizontal, 20)
.padding(.vertical, 24)
}
.themedBackground()
.navigationTitle("主题信息")
.navigationBarTitleDisplayMode(.inline)
}
}
// MARK: -
struct FeatureRow: View {
let icon: String
let title: String
let description: String
var body: some View {
HStack(alignment: .top, spacing: 12) {
ThemedIcon(icon, style: .accent, size: .medium)
.frame(width: 24, height: 24)
VStack(alignment: .leading, spacing: 4) {
ThemedText(title, style: .headline)
ThemedText(description, style: .secondary)
}
}
}
}
// MARK: -
struct QuickThemeToggle: View {
@EnvironmentObject var themeManager: ThemeManager
var body: some View {
Button(action: toggleTheme) {
HStack(spacing: 8) {
ThemedIcon(
themeManager.isDarkMode ? "moon.fill" : "sun.max.fill",
style: .accent,
size: .medium
)
ThemedText(
themeManager.isDarkMode ? "深色" : "浅色",
style: .secondary
)
}
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(Color.theme.surfaceBackground)
.cornerRadius(16)
}
.buttonStyle(PlainButtonStyle())
}
private func toggleTheme() {
withAnimation(AnimationConfig.smooth) {
if themeManager.systemFollowsDeviceTheme {
themeManager.setSystemFollowing(false)
}
themeManager.toggleTheme()
}
//
let impactFeedback = UIImpactFeedbackGenerator(style: .light)
impactFeedback.impactOccurred()
}
}
// MARK: -
#Preview("主题设置") {
ThemeSettingsView()
.environmentObject(ThemeManager())
}
#Preview("快速切换") {
QuickThemeToggle()
.environmentObject(ThemeManager())
.padding()
.themedBackground()
}
@@ -1,622 +0,0 @@
//
// UniverseView.swift
// EmotionMuseum
//
// Created by on 2025/6/13.
//
import SwiftUI
struct UniverseView: View {
@State private var showingProfile = false
@State private var showingSettings = false
@State private var showingAchievements = false
@State private var showingDataExport = false
var body: some View {
NavigationView {
ScrollView {
VStack(spacing: 24) {
//
UserProfileCard(onEditProfile: {
showingProfile = true
})
//
GrowthOverviewCard()
//
VStack(spacing: 16) {
MenuSection(title: "我的成长") {
VStack(spacing: 12) {
MenuRow(
icon: "trophy.fill",
title: "成就徽章",
subtitle: "查看你的成长里程碑",
color: .yellow,
action: { showingAchievements = true }
)
MenuRow(
icon: "chart.line.uptrend.xyaxis",
title: "成长报告",
subtitle: "详细的成长数据分析",
color: .blue,
action: { /* */ }
)
MenuRow(
icon: "calendar",
title: "情绪日历",
subtitle: "回顾你的情绪历程",
color: .green,
action: { /* */ }
)
}
}
MenuSection(title: "数据管理") {
VStack(spacing: 12) {
MenuRow(
icon: "square.and.arrow.up",
title: "导出数据",
subtitle: "导出你的个人数据",
color: .purple,
action: { showingDataExport = true }
)
MenuRow(
icon: "icloud.and.arrow.up",
title: "云端同步",
subtitle: "同步到iCloud",
color: .cyan,
action: { /* */ }
)
}
}
MenuSection(title: "设置") {
VStack(spacing: 12) {
MenuRow(
icon: "bell.fill",
title: "通知设置",
subtitle: "管理提醒和通知",
color: .orange,
action: { /* */ }
)
MenuRow(
icon: "lock.fill",
title: "隐私设置",
subtitle: "数据隐私和安全",
color: .red,
action: { /* */ }
)
MenuRow(
icon: "gearshape.fill",
title: "应用设置",
subtitle: "个性化设置",
color: .gray,
action: { showingSettings = true }
)
}
}
MenuSection(title: "帮助与支持") {
VStack(spacing: 12) {
MenuRow(
icon: "questionmark.circle.fill",
title: "使用帮助",
subtitle: "常见问题和使用指南",
color: .blue,
action: { /* */ }
)
MenuRow(
icon: "envelope.fill",
title: "联系我们",
subtitle: "反馈和建议",
color: .green,
action: { /* */ }
)
MenuRow(
icon: "star.fill",
title: "评价应用",
subtitle: "在App Store评价",
color: .yellow,
action: { /* App Store */ }
)
}
}
}
//
VStack(spacing: 8) {
Text("情绪博物馆")
.font(.subheadline)
.foregroundColor(.secondary)
Text("版本 1.0.0")
.font(.caption)
.foregroundColor(.secondary)
}
.padding(.top, 20)
}
.padding(.horizontal)
.padding(.vertical)
}
.navigationTitle("我的宇宙")
.navigationBarTitleDisplayMode(.large)
}
.sheet(isPresented: $showingProfile) {
ProfileEditView()
}
.sheet(isPresented: $showingSettings) {
SettingsView()
}
.sheet(isPresented: $showingAchievements) {
AchievementsView()
}
.sheet(isPresented: $showingDataExport) {
DataExportView()
}
}
}
// MARK: -
struct UserProfileCard: View {
let onEditProfile: () -> Void
var body: some View {
VStack(spacing: 16) {
HStack {
//
Button(action: onEditProfile) {
ZStack {
Circle()
.fill(LinearGradient(
colors: [.purple, .blue],
startPoint: .topLeading,
endPoint: .bottomTrailing
))
.frame(width: 80, height: 80)
Text("")
.font(.title)
.fontWeight(.bold)
.foregroundColor(.white)
}
}
VStack(alignment: .leading, spacing: 4) {
Text("华中敏")
.font(.title2)
.fontWeight(.bold)
Text("成长探索者")
.font(.subheadline)
.foregroundColor(.secondary)
HStack(spacing: 4) {
Image(systemName: "calendar")
.font(.caption)
Text("加入 30 天")
.font(.caption)
}
.foregroundColor(.secondary)
}
Spacer()
Button(action: onEditProfile) {
Image(systemName: "pencil")
.font(.title3)
.foregroundColor(.blue)
}
}
//
VStack(spacing: 8) {
HStack {
Text("成长等级")
.font(.subheadline)
.fontWeight(.medium)
Spacer()
Text("Lv.5")
.font(.subheadline)
.fontWeight(.bold)
.foregroundColor(.purple)
}
ProgressView(value: 0.7)
.progressViewStyle(LinearProgressViewStyle(tint: .purple))
HStack {
Text("距离下一级还需 150 经验")
.font(.caption)
.foregroundColor(.secondary)
Spacer()
}
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 16)
.fill(Color(UIColor.systemGray6))
)
}
}
// MARK: -
struct GrowthOverviewCard: View {
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("本周成长数据")
.font(.headline)
.fontWeight(.semibold)
HStack(spacing: 16) {
GrowthMetricView(
title: "情绪记录",
value: "12",
unit: "",
color: .blue,
icon: "heart.fill"
)
GrowthMetricView(
title: "疗愈时长",
value: "45",
unit: "分钟",
color: .purple,
icon: "timer.circle.fill"
)
}
HStack(spacing: 16) {
GrowthMetricView(
title: "课题进展",
value: "3",
unit: "",
color: .green,
icon: "checkmark.circle.fill"
)
GrowthMetricView(
title: "连续天数",
value: "7",
unit: "",
color: .orange,
icon: "flame.fill"
)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 16)
.fill(Color(.systemGray6))
)
}
}
struct GrowthMetricView: View {
let title: String
let value: String
let unit: String
let color: Color
let icon: String
var body: some View {
VStack(spacing: 8) {
HStack {
Image(systemName: icon)
.font(.title3)
.foregroundColor(color)
Spacer()
}
VStack(alignment: .leading, spacing: 2) {
HStack(alignment: .bottom, spacing: 2) {
Text(value)
.font(.title2)
.fontWeight(.bold)
Text(unit)
.font(.caption)
.foregroundColor(.secondary)
}
Text(title)
.font(.caption)
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.fill(Color(.systemBackground))
)
}
}
// MARK: -
struct MenuSection<Content: View>: View {
let title: String
let content: Content
init(title: String, @ViewBuilder content: () -> Content) {
self.title = title
self.content = content()
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text(title)
.font(.headline)
.fontWeight(.semibold)
content
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
struct MenuRow: View {
let icon: String
let title: String
let subtitle: String
let color: Color
let action: () -> Void
var body: some View {
Button(action: action) {
HStack(spacing: 16) {
ZStack {
RoundedRectangle(cornerRadius: 8)
.fill(color.opacity(0.1))
.frame(width: 40, height: 40)
Image(systemName: icon)
.font(.title3)
.foregroundColor(color)
}
VStack(alignment: .leading, spacing: 2) {
Text(title)
.font(.subheadline)
.fontWeight(.medium)
.foregroundColor(.primary)
Text(subtitle)
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
Image(systemName: "chevron.right")
.font(.caption)
.foregroundColor(.secondary)
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.fill(Color(.systemGray6))
)
}
.buttonStyle(PlainButtonStyle())
}
}
// MARK: -
struct ProfileEditView: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationView {
VStack {
Text("个人资料编辑")
.font(.title)
Text("这里是个人资料编辑页面")
.foregroundColor(.secondary)
}
.navigationTitle("编辑资料")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("取消") {
dismiss()
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("保存") {
dismiss()
}
}
}
}
}
}
struct SettingsView: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationView {
VStack {
Text("应用设置")
.font(.title)
Text("这里是应用设置页面")
.foregroundColor(.secondary)
}
.navigationTitle("设置")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("完成") {
dismiss()
}
}
}
}
}
}
struct AchievementsView: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationView {
ScrollView {
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 2), spacing: 16) {
ForEach(0..<6) { index in
AchievementCard(achievement: sampleAchievements[index])
}
}
.padding()
}
.navigationTitle("成就徽章")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("完成") {
dismiss()
}
}
}
}
}
private var sampleAchievements: [Achievement] {
[
Achievement(title: "初心者", description: "完成第一次情绪记录", category: .milestone, icon: "star.fill", rarity: .common, requirement: .conversationCount(1), targetValue: 1, unlockedAt: Date()),
Achievement(title: "坚持者", description: "连续7天记录情绪", category: .consistency, icon: "flame.fill", rarity: .rare, requirement: .consecutiveDays(7), targetValue: 7, unlockedAt: Date()),
Achievement(title: "探索者", description: "开始第一个课题", category: .growth, icon: "map.fill", rarity: .common, requirement: .topicCompletion(1), targetValue: 1, unlockedAt: Date()),
Achievement(title: "疗愈师", description: "完成10次脉轮疗愈", category: .emotion, icon: "heart.fill", rarity: .epic, requirement: .emotionRecordCount(10), targetValue: 10),
Achievement(title: "成长者", description: "完成一个完整课题", category: .growth, icon: "trophy.fill", rarity: .rare, requirement: .topicCompletion(5), targetValue: 5),
Achievement(title: "大师", description: "达到10级成长等级", category: .milestone, icon: "crown.fill", rarity: .legendary, requirement: .totalPoints(10000), targetValue: 10000)
]
}
}
struct DataExportView: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationView {
VStack(spacing: 20) {
Text("数据导出")
.font(.title)
Text("选择要导出的数据类型")
.foregroundColor(.secondary)
VStack(spacing: 12) {
ExportOptionRow(title: "情绪记录", description: "所有的情绪记录数据")
ExportOptionRow(title: "疗愈记录", description: "脉轮疗愈会话记录")
ExportOptionRow(title: "课题进展", description: "成长课题和进展数据")
ExportOptionRow(title: "成就数据", description: "解锁的成就和里程碑")
}
Spacer()
Button("导出数据") {
//
}
.buttonStyle(.borderedProminent)
}
.padding()
.navigationTitle("导出数据")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("取消") {
dismiss()
}
}
}
}
}
}
struct ExportOptionRow: View {
let title: String
let description: String
@State private var isSelected = false
var body: some View {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text(title)
.font(.subheadline)
.fontWeight(.medium)
Text(description)
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
Toggle("", isOn: $isSelected)
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.fill(Color(.systemGray6))
)
}
}
struct AchievementCard: View {
let achievement: Achievement
var body: some View {
VStack(spacing: 12) {
ZStack {
Circle()
.fill(achievement.isUnlocked ? achievement.rarity.color.opacity(0.2) : Color.gray.opacity(0.2))
.frame(width: 60, height: 60)
Image(systemName: achievement.icon)
.font(.title2)
.foregroundColor(achievement.isUnlocked ? achievement.rarity.color : .gray)
}
VStack(spacing: 4) {
Text(achievement.title)
.font(.subheadline)
.fontWeight(.medium)
.foregroundColor(achievement.isUnlocked ? .primary : .secondary)
Text(achievement.description)
.font(.caption)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.lineLimit(2)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.fill(Color(.systemGray6))
)
.opacity(achievement.isUnlocked ? 1.0 : 0.6)
}
}
// MARK: - 使DataModelsAchievement
#Preview {
UniverseView()
}
@@ -1,17 +0,0 @@
//
// EmotionMuseumTests.swift
// EmotionMuseumTests
//
// Created by on 2025/6/13.
//
import Testing
@testable import EmotionMuseum
struct EmotionMuseumTests {
@Test func example() async throws {
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
}
}
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
@@ -1,41 +0,0 @@
//
// EmotionMuseumUITests.swift
// EmotionMuseumUITests
//
// Created by on 2025/6/13.
//
import XCTest
final class EmotionMuseumUITests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
// In UI tests it is usually best to stop immediately when a failure occurs.
continueAfterFailure = false
// In UI tests its important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
@MainActor
func testExample() throws {
// UI tests must launch the application that they test.
let app = XCUIApplication()
app.launch()
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
@MainActor
func testLaunchPerformance() throws {
// This measures how long it takes to launch your application.
measure(metrics: [XCTApplicationLaunchMetric()]) {
XCUIApplication().launch()
}
}
}
@@ -1,33 +0,0 @@
//
// EmotionMuseumUITestsLaunchTests.swift
// EmotionMuseumUITests
//
// Created by on 2025/6/13.
//
import XCTest
final class EmotionMuseumUITestsLaunchTests: XCTestCase {
override class var runsForEachTargetApplicationUIConfiguration: Bool {
true
}
override func setUpWithError() throws {
continueAfterFailure = false
}
@MainActor
func testLaunch() throws {
let app = XCUIApplication()
app.launch()
// Insert steps here to perform after app launch but before taking a screenshot,
// such as logging into a test account or navigating somewhere in the app
let attachment = XCTAttachment(screenshot: app.screenshot())
attachment.name = "Launch Screen"
attachment.lifetime = .keepAlways
add(attachment)
}
}
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
Binary file not shown.
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 KiB

+104
View File
@@ -0,0 +1,104 @@
# 情感博物馆项目结构
## 📁 目录结构
```
emotion-museum/
├── 📁 backend/ # 后端微服务
│ ├── 📁 emotion-gateway/ # API网关服务
│ ├── 📁 emotion-user/ # 用户管理服务
│ ├── 📁 emotion-ai/ # AI聊天服务
│ ├── 📁 emotion-auth/ # 认证服务
│ ├── 📁 emotion-record/ # 记录管理服务
│ ├── 📁 emotion-growth/ # 成长跟踪服务
│ ├── 📁 emotion-explore/ # 探索服务
│ ├── 📁 emotion-reward/ # 奖励服务
│ ├── 📁 emotion-websocket/ # WebSocket服务
│ ├── 📁 emotion-stats/ # 统计服务
│ ├── 📁 emotion-common/ # 公共模块
│ ├── 🔧 build-all.sh # 构建脚本
│ ├── 🔧 deploy-all.sh # 综合部署脚本
│ ├── 🔧 deploy-remote.sh # 远程部署脚本
│ └── 📄 pom.xml # Maven父项目配置
├── 📁 web-flowith/ # 前端Vue项目
│ ├── 📁 src/ # 源代码
│ ├── 📁 public/ # 静态资源
│ ├── 🔧 deploy.sh # 前端部署脚本
│ └── 📄 package.json # 前端依赖配置
├── 📁 docs/ # 项目文档
│ ├── 📁 deployment/ # 部署相关文档
│ ├── 📁 architecture/ # 架构设计文档
│ └── 📁 database/ # 数据库相关文档
├── 📁 configs/ # 配置文件
│ ├── 📁 nginx/ # Nginx配置
│ ├── 📁 docker/ # Docker配置
│ └── 📁 env/ # 环境配置
├── 🔧 one-click-deploy.sh # 一键部署脚本
├── 🔧 restart-middleware.sh # 中间件重启脚本
├── 🔧 cleanup-project.sh # 项目清理脚本
└── 📄 README.md # 项目说明
```
## 🚀 快速开始
### 1. 一键部署
```bash
# 完整部署(前端+后端)
./one-click-deploy.sh
# 仅部署后端
./one-click-deploy.sh backend
# 仅部署前端
./one-click-deploy.sh frontend
# 健康检查
./one-click-deploy.sh check
```
### 2. 中间件管理
```bash
# 重启中间件(MySQL, Redis, Nacos
./restart-middleware.sh
```
### 3. 分步部署
```bash
# 构建后端
cd backend && ./build-all.sh
# 部署后端到远程
cd backend && ./deploy-remote.sh
# 部署前端
cd web-flowith && ./deploy.sh
```
## 📋 服务端口
| 服务 | 端口 | 描述 |
|------|------|------|
| emotion-gateway | 19000 | API网关 |
| emotion-user | 19001 | 用户服务 |
| emotion-ai | 19002 | AI服务 |
| emotion-record | 19003 | 记录服务 |
| emotion-growth | 19004 | 成长服务 |
| emotion-explore | 19005 | 探索服务 |
| emotion-reward | 19006 | 奖励服务 |
| emotion-websocket | 19007 | WebSocket服务 |
| emotion-auth | 19008 | 认证服务 |
| emotion-stats | 19009 | 统计服务 |
## 🔧 中间件端口
| 服务 | 端口 | 描述 |
|------|------|------|
| MySQL | 3306 | 数据库 |
| Redis | 6379 | 缓存 |
| Nacos | 8848 | 注册中心 |
## 📖 文档链接
- [部署指南](docs/deployment/)
- [架构设计](docs/architecture/)
- [数据库设计](docs/database/)
-175
View File
@@ -1,175 +0,0 @@
# 服务器部署检查清单
## 📋 部署前检查
### 🖥️ 服务器要求
- [ ] **操作系统**: Linux (Ubuntu 18.04+, CentOS 7+, Debian 9+)
- [ ] **CPU**: 2核心以上
- [ ] **内存**: 4GB以上(推荐8GB
- [ ] **磁盘**: 20GB以上可用空间
- [ ] **网络**: 稳定的互联网连接
### 🔧 软件环境
- [ ] **Docker**: 20.10+ 已安装
- [ ] **Docker Compose**: 1.29+ 已安装
- [ ] **Git**: 已安装(可选)
- [ ] **Curl/Wget**: 已安装
### 🌐 网络配置
- [ ] **端口开放**: 80, 443 已开放
- [ ] **防火墙**: 已配置允许HTTP/HTTPS流量
- [ ] **域名解析**: 域名已正确解析到服务器IP(如有)
- [ ] **SSL证书**: 已准备好SSL证书文件(生产环境)
## 📦 部署包准备
### 📁 文件检查
- [ ] **部署包**: `emotion-museum-1.0.0-20250713_111829.tar.gz` 已下载
- [ ] **校验和**: SHA256校验通过
- [ ] **解压**: 部署包已成功解压
- [ ] **权限**: 脚本文件已设置执行权限
### ⚙️ 配置文件
- [ ] **环境变量**: `.env` 文件已配置
- [ ] **Coze API**: API Token 已设置
- [ ] **数据库密码**: 已修改默认密码
- [ ] **域名配置**: Nginx配置中的域名已更新(生产环境)
## 🚀 部署执行
### 🔄 部署步骤
- [ ] **1. 环境检查**: `./quick-deploy.sh` 环境检查通过
- [ ] **2. Docker安装**: Docker和Docker Compose安装成功
- [ ] **3. 配置生成**: 配置文件生成成功
- [ ] **4. 镜像构建**: Docker镜像构建成功
- [ ] **5. 服务启动**: 所有容器启动成功
### 📊 服务状态
- [ ] **MySQL**: 容器运行正常,数据库连接成功
- [ ] **Redis**: 容器运行正常,缓存服务可用
- [ ] **Nacos**: 容器运行正常,注册中心可访问
- [ ] **Gateway**: 容器运行正常,网关服务可用
- [ ] **AI Service**: 容器运行正常,AI服务可用
- [ ] **User Service**: 容器运行正常,用户服务可用
- [ ] **Frontend**: 容器运行正常,前端应用可访问
- [ ] **Nginx**: 容器运行正常,反向代理工作正常
## ✅ 部署验证
### 🌐 访问测试
- [ ] **前端首页**: http://your-domain.com 可正常访问
- [ ] **API网关**: http://your-domain.com:9000/actuator/health 返回正常
- [ ] **Nacos控制台**: http://your-domain.com:8848/nacos 可正常登录
- [ ] **API文档**: http://your-domain.com:9000/doc.html 可正常访问
### 🔍 功能测试
- [ ] **用户注册**: 新用户注册功能正常
- [ ] **用户登录**: 用户登录功能正常
- [ ] **AI对话**: AI聊天功能正常
- [ ] **数据存储**: 对话记录正常保存
- [ ] **情绪分析**: 情绪分析功能正常
### 📈 性能测试
- [ ] **响应时间**: 页面加载时间 < 3秒
- [ ] **API响应**: API接口响应时间 < 1秒
- [ ] **并发测试**: 支持预期的并发用户数
- [ ] **资源使用**: CPU和内存使用率在合理范围
## 🔒 安全配置
### 🛡️ 基础安全
- [ ] **默认密码**: 所有默认密码已修改
- [ ] **防火墙**: 只开放必要端口
- [ ] **用户权限**: 使用非root用户运行服务
- [ ] **文件权限**: 敏感文件权限设置正确
### 🔐 HTTPS配置(生产环境)
- [ ] **SSL证书**: 证书文件已正确放置
- [ ] **Nginx配置**: HTTPS配置已启用
- [ ] **HTTP重定向**: HTTP自动重定向到HTTPS
- [ ] **证书验证**: SSL证书验证通过
### 🔑 API安全
- [ ] **JWT配置**: JWT密钥已设置
- [ ] **CORS配置**: 跨域配置正确
- [ ] **限流配置**: API限流规则已启用
- [ ] **访问日志**: 访问日志记录正常
## 📊 监控配置
### 📈 服务监控
- [ ] **健康检查**: `./manage.sh health` 所有服务健康
- [ ] **日志收集**: 日志文件正常生成
- [ ] **资源监控**: `./manage.sh monitor` 监控面板正常
- [ ] **告警配置**: 异常告警机制已配置(可选)
### 💾 数据备份
- [ ] **备份脚本**: `./manage.sh backup` 备份功能正常
- [ ] **备份策略**: 定期备份计划已制定
- [ ] **恢复测试**: 数据恢复功能已测试
- [ ] **备份存储**: 备份文件存储位置已确定
## 📝 文档记录
### 📋 部署记录
- [ ] **部署时间**: 记录部署完成时间
- [ ] **版本信息**: 记录部署的版本号
- [ ] **配置信息**: 记录重要配置参数
- [ ] **访问信息**: 记录访问地址和账号
### 📖 运维文档
- [ ] **管理命令**: 熟悉 `./manage.sh` 各项命令
- [ ] **故障排除**: 了解常见问题解决方案
- [ ] **更新流程**: 了解服务更新流程
- [ ] **联系方式**: 记录技术支持联系方式
## 🎯 部署后任务
### 🔧 优化配置
- [ ] **性能调优**: 根据实际负载调整配置
- [ ] **缓存策略**: 优化Redis缓存配置
- [ ] **数据库优化**: 调整MySQL配置参数
- [ ] **网络优化**: 优化Nginx配置
### 📊 监控设置
- [ ] **日志轮转**: 配置日志文件轮转
- [ ] **磁盘清理**: 设置定期清理任务
- [ ] **性能监控**: 配置性能监控工具
- [ ] **告警通知**: 设置异常告警通知
### 🔄 维护计划
- [ ] **更新计划**: 制定定期更新计划
- [ ] **备份计划**: 制定数据备份计划
- [ ] **安全审计**: 制定安全审计计划
- [ ] **容量规划**: 制定容量扩展计划
## ⚠️ 注意事项
### 🚨 重要提醒
1. **Coze API Token**: 必须配置正确的API Token,否则AI功能无法使用
2. **数据库密码**: 生产环境必须修改默认密码
3. **防火墙配置**: 确保只开放必要的端口
4. **SSL证书**: 生产环境强烈建议使用HTTPS
5. **定期备份**: 重要数据必须定期备份
### 📞 紧急联系
- **技术支持**: support@emotion-museum.com
- **紧急热线**: 400-xxx-xxxx
- **在线文档**: https://docs.emotion-museum.com
---
## ✅ 部署完成确认
**部署工程师**: ________________
**部署时间**: ________________
**版本号**: emotion-museum-1.0.0-20250713_111829
**服务器IP**: ________________
**域名**: ________________
**签名确认**: ________________
**日期**: ________________
---
**🎉 恭喜完成部署!请妥善保存此检查清单作为部署记录。**
-269
View File
@@ -1,269 +0,0 @@
# Nacos配置优化总结
## 概述
根据当前更新后的模块端口号和Nacos配置要求,已完成所有后台模块的Nacos配置优化,确保所有服务可以正确注册到Nacos上,并为每个模块创建了本地、测试、生产三套环境配置文件。
## 完成的工作
### 1. 端口配置统一
| 服务名称 | 端口 | 状态 | 描述 |
|---------|------|------|------|
| emotion-gateway | 19000 | ✅ 已配置 | 网关服务 |
| emotion-user | 19001 | ✅ 已配置 | 用户服务(包含认证) |
| emotion-ai | 19002 | ✅ 已配置 | AI对话服务 |
| emotion-record | 19003 | ✅ 已修正 | 情绪记录服务 |
| emotion-growth | 19004 | ✅ 已配置 | 成长课题服务 |
| emotion-explore | 19005 | ✅ 已配置 | 地图探索服务 |
| emotion-reward | 19006 | ✅ 已配置 | 成就奖励服务 |
| emotion-websocket | 19007 | ✅ 已配置 | WebSocket聊天服务 |
| emotion-stats | 19008 | ✅ 已配置 | 统计分析服务 |
### 2. Nacos配置优化
#### 主配置文件更新 (`application.yml`)
- ✅ 启用Nacos服务发现 (`enabled: true`)
- ✅ 添加认证配置 (`username/password`)
- ✅ 配置服务元数据 (`metadata`)
- ✅ 设置心跳和超时参数
- ✅ 配置集群和权重信息
#### 环境配置文件创建
为每个服务创建了三套环境配置:
**本地环境** (`application-local.yml`)
```yaml
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace:
group: DEFAULT_GROUP
enabled: true
username: nacos
password: nacos
```
**测试环境** (`application-test.yml`)
```yaml
spring:
cloud:
nacos:
discovery:
server-addr: 47.111.10.27:8848
namespace: test
group: DEFAULT_GROUP
enabled: true
username: nacos
password: nacos
```
**生产环境** (`application-prod.yml`)
```yaml
spring:
cloud:
nacos:
discovery:
server-addr: 47.111.10.27:8848
namespace: prod
group: DEFAULT_GROUP
enabled: true
username: nacos
password: nacos
```
### 3. 网关配置优化
#### 路由配置更新
- ✅ 使用负载均衡 (`lb://service-name`)
- ✅ 启用服务发现 (`locator.enabled: true`)
- ✅ 配置跨域支持 (`globalcors`)
- ✅ 支持WebSocket路由
#### 完整路由列表
```yaml
routes:
# 用户相关服务
- /user/** → lb://emotion-user
- /captcha/** → lb://emotion-user
- /oauth/** → lb://emotion-user
# AI相关服务
- /ai/** → lb://emotion-ai
- /websocket/** → lb://emotion-websocket
- /ws/** → lb://emotion-websocket
# 业务功能服务
- /record/** → lb://emotion-record
- /growth/** → lb://emotion-growth
- /explore/** → lb://emotion-explore
- /reward/** → lb://emotion-reward
- /stats/** → lb://emotion-stats
```
### 4. 特殊服务配置
#### emotion-ai服务
- ✅ 添加Coze平台配置
- ✅ 配置功能开关
- ✅ AI模型参数配置
#### emotion-websocket服务
- ✅ 添加WebSocket配置
- ✅ 配置STOMP和SockJS
- ✅ 设置消息代理
### 5. 数据库和Redis配置
#### 环境差异化配置
**本地环境**
```yaml
datasource:
url: jdbc:mysql://localhost:3306/emotion_museum
username: root
password: 123456
redis:
host: localhost
port: 6379
password:
```
**测试/生产环境**
```yaml
datasource:
url: jdbc:mysql://47.111.10.27:3306/emotion_museum
username: root
password: EmotionMuseum2025*#
redis:
host: 47.111.10.27
port: 6379
password: EmotionMuseum2025*#
```
## 工具脚本
### 1. 批量配置生成脚本
- **文件**: `backend/update-nacos-config.sh`
- **功能**: 批量为所有服务生成Nacos配置文件
- **使用**: `./backend/update-nacos-config.sh`
### 2. 配置验证脚本
- **文件**: `backend/verify-nacos-config.sh`
- **功能**: 验证所有服务的配置文件完整性
- **使用**: `./backend/verify-nacos-config.sh`
## Nacos服务器配置
### 认证配置
根据提供的Nacos配置,已配置:
```properties
nacos.core.auth.enabled=false
nacos.core.auth.admin.enabled=true
nacos.core.auth.console.enabled=true
nacos.core.auth.server.identity.key=nacosServerIdentity
nacos.core.auth.server.identity.value=Peanut2817*#
```
### 客户端认证
所有服务配置了统一的认证信息:
- **用户名**: nacos
- **密码**: nacos
## 启动方式
### 1. 指定环境启动
```bash
# 本地环境
mvn spring-boot:run -Dspring-boot.run.profiles=local
# 测试环境
mvn spring-boot:run -Dspring-boot.run.profiles=test
# 生产环境
mvn spring-boot:run -Dspring-boot.run.profiles=prod
```
### 2. JAR包启动
```bash
# 本地环境
java -jar app.jar --spring.profiles.active=local
# 测试环境
java -jar app.jar --spring.profiles.active=test
# 生产环境
java -jar app.jar --spring.profiles.active=prod
```
## 验证结果
### 配置验证通过
```
===========================================
验证结果统计
===========================================
总服务数: 9
验证成功: 9
验证失败: 0
🎉 所有服务配置验证通过!
```
### 服务注册验证
启动服务后,可通过以下方式验证:
1. **Nacos控制台**: http://47.111.10.27:8848/nacos
2. **服务列表**: 查看所有服务是否正确注册
3. **健康检查**: 确认服务状态为UP
## 环境隔离
### 命名空间配置
- **本地环境**: 默认命名空间 (空)
- **测试环境**: test命名空间
- **生产环境**: prod命名空间
### 配置隔离
- 不同环境使用不同的数据库和Redis实例
- 日志级别按环境调整
- 服务发现配置环境隔离
## 监控和日志
### 日志配置
- **本地环境**: DEBUG级别,详细日志
- **测试环境**: INFO级别,适中日志
- **生产环境**: WARN级别,精简日志
### 日志文件
- 格式: `logs/{service-name}-{env}.log`
- 示例: `logs/emotion-user-local.log`
## 后续优化建议
1. **配置中心**: 启用Nacos配置中心功能
2. **服务监控**: 集成Prometheus和Grafana
3. **链路追踪**: 添加Sleuth和Zipkin
4. **熔断降级**: 集成Sentinel
5. **安全加固**: 配置服务间认证
## 总结
**完成项目**:
- 所有9个微服务的Nacos配置已完成
- 端口配置已统一和修正
- 三套环境配置文件已创建
- 网关路由配置已优化
- 配置验证100%通过
**关键特性**:
- 支持环境隔离
- 自动服务发现
- 负载均衡路由
- 跨域支持
- WebSocket支持
现在所有服务都可以正确注册到Nacos,通过网关进行统一访问,支持多环境部署!🚀
-270
View File
@@ -1,270 +0,0 @@
# Nacos配置最终总结
## 概述
已完成所有后台模块的Nacos配置优化,包括端口统一、环境配置文件创建和密码配置更新。所有服务现在可以正确注册到Nacos并通过网关进行统一访问。
## 最终配置状态
### 1. 服务端口配置 ✅
| 服务名称 | 端口 | 状态 | 描述 |
|---------|------|------|------|
| emotion-gateway | 19000 | ✅ 已配置 | 网关服务 |
| emotion-user | 19001 | ✅ 已配置 | 用户服务(包含认证) |
| emotion-ai | 19002 | ✅ 已配置 | AI对话服务 |
| emotion-record | 19003 | ✅ 已修正 | 情绪记录服务 |
| emotion-growth | 19004 | ✅ 已配置 | 成长课题服务 |
| emotion-explore | 19005 | ✅ 已配置 | 地图探索服务 |
| emotion-reward | 19006 | ✅ 已配置 | 成就奖励服务 |
| emotion-websocket | 19007 | ✅ 已配置 | WebSocket聊天服务 |
| emotion-stats | 19008 | ✅ 已配置 | 统计分析服务 |
### 2. Nacos密码配置 ✅
#### 环境密码配置
- **本地环境** (`application-local.yml`): `Peanut2817*#`
- **测试环境** (`application-test.yml`): `EmotionMuseum2025`
- **生产环境** (`application-prod.yml`): `EmotionMuseum2025`
#### 验证结果
```bash
# 本地环境密码验证
grep "password: Peanut2817*#" backend/emotion-*/src/main/resources/application-local.yml
# 结果: 18个匹配项 (9个服务 × 2个配置项)
# 测试环境密码验证
grep "password: EmotionMuseum2025" backend/emotion-*/src/main/resources/application-test.yml
# 结果: 18个匹配项
# 生产环境密码验证
grep "password: EmotionMuseum2025" backend/emotion-*/src/main/resources/application-prod.yml
# 结果: 18个匹配项
```
### 3. 环境配置文件 ✅
每个服务都包含完整的三套环境配置:
#### 本地环境配置示例
```yaml
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
```
#### 测试环境配置示例
```yaml
spring:
cloud:
nacos:
discovery:
server-addr: 47.111.10.27:8848
namespace: test
group: DEFAULT_GROUP
enabled: true
username: nacos
password: EmotionMuseum2025
metadata:
version: 1.0.0
zone: test
```
#### 生产环境配置示例
```yaml
spring:
cloud:
nacos:
discovery:
server-addr: 47.111.10.27:8848
namespace: prod
group: DEFAULT_GROUP
enabled: true
username: nacos
password: EmotionMuseum2025
metadata:
version: 1.0.0
zone: prod
```
### 4. 网关路由配置 ✅
#### 完整路由映射
```yaml
routes:
# 用户相关服务
- /user/** → lb://emotion-user:19001
- /captcha/** → lb://emotion-user:19001
- /oauth/** → lb://emotion-user:19001
# AI相关服务
- /ai/** → lb://emotion-ai:19002
- /websocket/** → lb://emotion-websocket:19007
- /ws/** → lb://emotion-websocket:19007 (WebSocket)
# 业务功能服务
- /record/** → lb://emotion-record:19003
- /growth/** → lb://emotion-growth:19004
- /explore/** → lb://emotion-explore:19005
- /reward/** → lb://emotion-reward:19006
- /stats/** → lb://emotion-stats:19008
```
#### 特殊配置
- ✅ 跨域支持 (`globalcors`)
- ✅ WebSocket协议升级支持
- ✅ 负载均衡 (`lb://`)
- ✅ 服务发现集成
### 5. 特殊服务配置 ✅
#### emotion-ai服务
```yaml
# Coze平台配置
coze:
base-url: https://api.coze.cn
bot-id: 7523042446285439016
workflow-id: 7523047462895796287
token: pat_GCR4qKzqpf90wMCvKsldMrB18KG3QsLDci65bZthssKsbLxu8X70BKYumleDcabO
# 功能开关
features:
emotion-analysis:
enabled: false
chat:
enabled: true
```
#### emotion-websocket服务
```yaml
# WebSocket配置
websocket:
allowed-origins: "*"
sockjs:
enabled: true
heartbeat-time: 25000
stomp:
broker:
enabled: true
destinations: ["/topic", "/queue"]
application-destination-prefixes: ["/app"]
```
## 启动方式
### 1. 环境指定启动
```bash
# 本地环境
mvn spring-boot:run -Dspring-boot.run.profiles=local
# 测试环境
mvn spring-boot:run -Dspring-boot.run.profiles=test
# 生产环境
mvn spring-boot:run -Dspring-boot.run.profiles=prod
```
### 2. JAR包启动
```bash
# 本地环境
java -jar app.jar --spring.profiles.active=local
# 测试环境
java -jar app.jar --spring.profiles.active=test
# 生产环境
java -jar app.jar --spring.profiles.active=prod
```
## 验证工具
### 1. 配置验证脚本
```bash
./backend/verify-nacos-config.sh
```
### 2. 密码更新脚本
```bash
./backend/update-nacos-passwords.sh
```
### 3. 网关路由测试
```bash
./backend/emotion-gateway/test-gateway-routes.sh
```
## 环境隔离
### 命名空间配置
- **本地环境**: 默认命名空间 (空字符串)
- **测试环境**: `test` 命名空间
- **生产环境**: `prod` 命名空间
### 数据库配置
- **本地环境**: `localhost:3306`, 密码: `123456`
- **测试环境**: `47.111.10.27:3306`, 密码: `EmotionMuseum2025*#`
- **生产环境**: `47.111.10.27:3306`, 密码: `EmotionMuseum2025*#`
### Redis配置
- **本地环境**: `localhost:6379`, 无密码
- **测试环境**: `47.111.10.27:6379`, 密码: `EmotionMuseum2025*#`
- **生产环境**: `47.111.10.27:6379`, 密码: `EmotionMuseum2025*#`
## 配置文件清单
### 每个服务包含的配置文件
```
backend/{service-name}/src/main/resources/
├── application.yml # 主配置文件
├── application-local.yml # 本地环境配置
├── application-test.yml # 测试环境配置
└── application-prod.yml # 生产环境配置
```
### 总计配置文件数量
- **9个服务** × **4个配置文件** = **36个配置文件**
- 所有配置文件都已创建并验证通过 ✅
## 最终验证结果
```
===========================================
验证结果统计
===========================================
总服务数: 9
验证成功: 9
验证失败: 0
🎉 所有服务配置验证通过!
```
## 总结
**已完成的工作**:
1. 统一了所有9个微服务的端口配置
2. 为每个服务创建了完整的三套环境配置文件
3. 配置了正确的Nacos认证密码
4. 优化了网关路由配置,支持负载均衡和WebSocket
5. 添加了特殊服务的专用配置
6. 创建了验证和管理工具脚本
**关键特性**:
- 完整的环境隔离 (local/test/prod)
- 统一的服务发现和注册
- 负载均衡路由
- WebSocket支持
- 跨域配置
- 安全认证
现在所有服务都可以正确注册到Nacos,通过网关进行统一访问,支持多环境部署!🚀
**下一步**: 提交代码到远程仓库
-156
View File
@@ -1,156 +0,0 @@
# 情感博物馆项目优化总结
## 已完成的工作
### 1. 创建了emotion-auth统一认证模块
- ✅ 创建了emotion-auth模块的基础结构
- ✅ 移动了认证相关的代码到auth模块
- ✅ 配置了auth模块的pom.xml和Dockerfile
- ✅ 更新了父pom.xml包含auth模块
### 2. 优化了配置文件管理
- ✅ 统一了所有服务的application.yml配置
- ✅ 添加了环境变量支持:`spring.profiles.active: ${SPRING_PROFILES_ACTIVE:local}`
- ✅ 创建了application-local.yml本地环境配置文件
- ✅ 配置了Bean覆盖和循环引用支持
### 3. 优化了启动脚本
- ✅ 创建了支持环境参数的启动脚本:`./start-services.sh [env]`
- ✅ 默认使用local环境,支持dev、prod等环境切换
- ✅ 添加了基础服务检查(MySQL、Redis)
- ✅ 添加了服务状态检查和日志输出
### 4. 统一了Coze API配置
- ✅ 在AI服务中配置了统一的Coze API参数
- ✅ 所有环境使用相同的Coze配置
### 5. 清理了无用文件
- ✅ 删除了测试文件和重复的配置文件
- ✅ 删除了不再使用的启动脚本
### 6. 前端网关配置
- ✅ 更新了前端vite.config.js,统一通过网关访问后端
- ✅ 更新了网关路由配置,支持所有服务的路由转发
- ✅ 创建了.env.local环境配置文件
## 已解决的问题
### 1. 循环依赖问题 ✅
**问题描述:** 用户服务存在Bean循环依赖问题
```
jwtAuthenticationFilter -> userDetailsServiceImpl -> userServiceImpl -> securityConfig -> jwtAuthenticationFilter
```
**解决方案:**
1. ✅ 创建独立的AuthenticationConfig类,分离认证相关Bean配置
2. ✅ 使用@Lazy注解延迟加载关键依赖
3. ✅ 通过ApplicationContext获取Bean,避免直接依赖
4. ✅ 修复验证码配置中的初始化问题
### 2. 服务启动成功 ✅
所有核心服务现在都能正常启动:
- ✅ 用户服务 (19001) - 运行正常
- ✅ AI服务 (19002) - 运行正常
- ✅ 网关服务 (19000) - 运行正常
### 3. 配置文件问题 ✅
- ✅ 修复了重复的profiles配置
- ✅ 统一了环境变量配置格式
- ✅ 修复了pom.xml中缺失的主类配置
## 下一步工作计划
### 优先级1:完善网关路由和安全配置 🔄
1. **修复网关路由问题**
- 配置用户服务的安全白名单,允许actuator端点访问
- 为AI服务添加actuator端点配置
- 测试网关路由功能
2. **完善前端集成**
- 测试前端通过网关访问后端API
- 验证用户注册登录功能
- 测试AI对话功能
### 优先级2:完善emotion-auth模块
1. **实现认证服务接口**
- 完成AuthService实现类
- 实现JWT Token管理
- 实现用户认证逻辑
2. **配置服务间调用**
- 配置Feign客户端
- 实现服务间认证传递
- 配置统一的异常处理
### 优先级3:扩展其他微服务
1. **启动其他微服务**
- emotion-record (记录服务)
- emotion-growth (成长服务)
- emotion-explore (探索服务)
- emotion-reward (奖励服务)
- emotion-stats (统计服务)
2. **完整功能测试**
- 测试所有微服务的启动和通信
- 验证完整的业务流程
- 性能测试和优化
## 使用说明
### 启动服务
```bash
# 使用默认local环境启动
./start-services.sh
# 使用指定环境启动
./start-services.sh dev
./start-services.sh prod
```
### 停止服务
```bash
./stop-services.sh
```
### 查看日志
```bash
# 查看用户服务日志
tail -f logs/emotion-user-local.log
# 查看AI服务日志
tail -f logs/emotion-ai-local.log
# 查看网关服务日志
tail -f logs/emotion-gateway-local.log
```
### 环境变量配置
可以通过环境变量覆盖默认配置:
- `SPRING_PROFILES_ACTIVE`: 指定激活的配置文件
- `MYSQL_HOST`, `MYSQL_PORT`, `MYSQL_USERNAME`, `MYSQL_PASSWORD`: 数据库配置
- `REDIS_HOST`, `REDIS_PORT`, `REDIS_PASSWORD`: Redis配置
- `COZE_API_TOKEN`: Coze API令牌
## 项目结构
```
backend/
├── emotion-common/ # 公共模块
├── emotion-auth/ # 认证授权服务 (新增)
├── emotion-user/ # 用户服务
├── emotion-ai/ # AI服务
├── emotion-gateway/ # 网关服务
├── emotion-record/ # 记录服务
├── emotion-growth/ # 成长服务
├── emotion-explore/ # 探索服务
├── emotion-reward/ # 奖励服务
├── emotion-stats/ # 统计服务
├── start-services.sh # 启动脚本
├── stop-services.sh # 停止脚本
└── logs/ # 日志目录
```
## 注意事项
1. 确保MySQL和Redis服务已启动
2. 首次启动可能需要较长时间进行编译
3. 如遇到端口占用,脚本会自动跳过该服务
4. 建议在解决循环依赖问题后再进行完整的功能测试
+7 -2
View File
@@ -192,10 +192,15 @@ generate_build_report() {
for service_info in "${SERVICES[@]}"; do for service_info in "${SERVICES[@]}"; do
service_name=$(echo $service_info | cut -d':' -f1) service_name=$(echo $service_info | cut -d':' -f1)
jar_file="${service_name}/target/${service_name}-1.0.0.jar" jar_file="${service_name}/target/${service_name}-1.0.0.jar"
if [ -f "$jar_file" ]; then if [ -f "$jar_file" ]; then
jar_size=$(du -h "$jar_file" | cut -f1) jar_size=$(du -h "$jar_file" | cut -f1)
jar_bytes=$(du -b "$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)) total_size=$((total_size + jar_bytes))
printf "%-20s ${GREEN}%-10s${NC} %-10s %s\n" "$service_name" "✅ 成功" "$jar_size" "$jar_file" printf "%-20s ${GREEN}%-10s${NC} %-10s %s\n" "$service_name" "✅ 成功" "$jar_size" "$jar_file"
else else
+1 -7
View File
@@ -73,9 +73,6 @@ if [ -n "$TEST_SINGLE_SERVICE" ]; then
fi fi
# 部署状态跟踪 # 部署状态跟踪
declare -A DEPLOYMENT_STATUS
declare -A DEPLOYMENT_ERRORS
declare -A DEPLOYMENT_TIMES
TOTAL_SERVICES=${#SERVICES[@]} TOTAL_SERVICES=${#SERVICES[@]}
SUCCESSFUL_DEPLOYMENTS=0 SUCCESSFUL_DEPLOYMENTS=0
FAILED_DEPLOYMENTS=0 FAILED_DEPLOYMENTS=0
@@ -214,13 +211,10 @@ deploy_service() {
local start_time=$(date +%s) local start_time=$(date +%s)
log_info "开始部署服务到远程服务器: $service_name" log_info "开始部署服务到远程服务器: $service_name"
DEPLOYMENT_STATUS[$service_name]="DEPLOYING"
# 先传输jar包 # 先传输jar包
if ! transfer_jar_to_remote $service_name; then if ! transfer_jar_to_remote $service_name; then
local error_msg="jar包传输失败" log_error "jar包传输失败"
DEPLOYMENT_STATUS[$service_name]="FAILED"
DEPLOYMENT_ERRORS[$service_name]="$error_msg"
return 1 return 1
fi fi
+2 -11
View File
@@ -58,9 +58,6 @@ SERVICES=(
) )
# 部署状态跟踪 # 部署状态跟踪
declare -A DEPLOYMENT_STATUS
declare -A DEPLOYMENT_ERRORS
declare -A DEPLOYMENT_TIMES
TOTAL_SERVICES=${#SERVICES[@]} TOTAL_SERVICES=${#SERVICES[@]}
SUCCESSFUL_DEPLOYMENTS=0 SUCCESSFUL_DEPLOYMENTS=0
FAILED_DEPLOYMENTS=0 FAILED_DEPLOYMENTS=0
@@ -177,14 +174,11 @@ deploy_service_to_remote() {
local start_time=$(date +%s) local start_time=$(date +%s)
log_info "部署服务到远程: $service_name" log_info "部署服务到远程: $service_name"
DEPLOYMENT_STATUS[$service_name]="DEPLOYING"
# 验证远程jar包存在 # 验证远程jar包存在
if ! ssh 'root@47.111.10.27' "test -f $REMOTE_BUILD_DIR/${service_name}-1.0.0.jar"; then if ! ssh 'root@47.111.10.27' "test -f $REMOTE_BUILD_DIR/${service_name}-1.0.0.jar"; then
local error_msg="远程jar包不存在" local error_msg="远程jar包不存在"
log_error "$error_msg" log_error "$error_msg"
DEPLOYMENT_STATUS[$service_name]="FAILED"
DEPLOYMENT_ERRORS[$service_name]="$error_msg"
return 1 return 1
fi fi
@@ -253,8 +247,7 @@ deploy_service_to_remote() {
# 记录成功状态 # 记录成功状态
local end_time=$(date +%s) local end_time=$(date +%s)
local duration=$((end_time - start_time)) local duration=$((end_time - start_time))
DEPLOYMENT_STATUS[$service_name]="SUCCESS" log_info "服务 $service_name 部署成功,耗时: ${duration}s"
DEPLOYMENT_TIMES[$service_name]="${duration}s"
return 0 return 0
else else
local error_msg="服务启动失败" local error_msg="服务启动失败"
@@ -265,9 +258,7 @@ deploy_service_to_remote() {
# 记录失败状态 # 记录失败状态
local end_time=$(date +%s) local end_time=$(date +%s)
local duration=$((end_time - start_time)) local duration=$((end_time - start_time))
DEPLOYMENT_STATUS[$service_name]="FAILED" log_error "服务 $service_name 部署失败,耗时: ${duration}s"
DEPLOYMENT_ERRORS[$service_name]="$error_msg: $error_logs"
DEPLOYMENT_TIMES[$service_name]="${duration}s"
return 1 return 1
fi fi
} }
-594
View File
@@ -1,594 +0,0 @@
#!/bin/bash
# 情感博物馆 - 全服务容器化部署脚本
# 作者: emotion-museum
# 日期: 2025-07-18
# 支持Jenkins CI/CD部署
# 不要在遇到错误时立即退出,让所有模块都尝试部署
set +e
# 配置变量 - 支持Jenkins环境变量覆盖
REMOTE_HOST="${DEPLOY_HOST:-'root@47.111.10.27'}"
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"
)
# 部署状态跟踪
declare -A DEPLOYMENT_STATUS
declare -A DEPLOYMENT_ERRORS
declare -A DEPLOYMENT_TIMES
TOTAL_SERVICES=${#SERVICES[@]}
SUCCESSFUL_DEPLOYMENTS=0
FAILED_DEPLOYMENTS=0
# 检查远程服务器连接
check_remote_connection() {
log_info "检查远程服务器连接..."
if ssh -o ConnectTimeout=10 'root@47.111.10.27' "echo 'Connection successful'" > /dev/null 2>&1; then
log_success "远程服务器连接正常"
else
log_error "无法连接到远程服务器 'root@47.111.10.27'"
exit 1
fi
}
# 创建远程目录
create_remote_directories() {
log_info "创建远程目录结构..."
ssh 'root@47.111.10.27' "
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@47.111.10.27' "rm -f $REMOTE_BUILD_DIR/${service_name}-*.jar"
# 上传新jar包
log_info "上传jar包到远程服务器..."
if scp "$jar_file" 'root@47.111.10.27':$REMOTE_BUILD_DIR/${service_name}-1.0.0.jar; then
log_success "jar包传输成功: $service_name"
# 验证远程jar包
local remote_size=$(ssh 'root@47.111.10.27' "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"
DEPLOYMENT_STATUS[$service_name]="DEPLOYING"
# 先传输jar包
if ! transfer_jar_to_remote $service_name; then
local error_msg="jar包传输失败"
DEPLOYMENT_STATUS[$service_name]="FAILED"
DEPLOYMENT_ERRORS[$service_name]="$error_msg"
return 1
fi
# 验证远程jar包存在
log_info "验证远程jar包: $service_name"
if ! ssh 'root@47.111.10.27' "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@47.111.10.27' "
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@47.111.10.27' "
# 复制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@47.111.10.27' "
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=47.111.10.27 \\
-e MYSQL_PORT=3306 \\
-e MYSQL_DATABASE=emotion_museum \\
-e MYSQL_USERNAME=root \\
-e MYSQL_PASSWORD='EmotionMuseum2025*#' \\
-e REDIS_HOST=47.111.10.27 \\
-e REDIS_PORT=6379 \\
-e REDIS_PASSWORD= \\
-e REDIS_DATABASE=0 \\
-e NACOS_SERVER_ADDR=47.111.10.27: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@47.111.10.27' "docker ps | grep ${service_name}" > /dev/null 2>&1; then
log_success "服务 $service_name 启动成功"
# 显示容器日志
log_info "显示服务日志 最后10行: $service_name"
ssh 'root@47.111.10.27' "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@47.111.10.27' "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@47.111.10.27' "cat > $REMOTE_DOCKER_COMPOSE_DIR/Dockerfile.${service_name} << 'EOF'
FROM openjdk:17-jre-slim
# 设置工作目录
WORKDIR /app
# 安装必要的工具
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# 复制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@47.111.10.27' "
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@47.111.10.27' "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://47.111.10.27:$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@47.111.10.27' "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 "$@"
-209
View File
@@ -1,209 +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
# 配置
PROJECT_ROOT=$(pwd)
SERVICES=("emotion-gateway" "emotion-user" "emotion-ai" "emotion-record" "emotion-growth" "emotion-explore" "emotion-reward" "emotion-stats")
PID_FILE="$PROJECT_ROOT/.dev-pids"
LOG_DIR="$PROJECT_ROOT/dev-logs"
# 创建日志目录
mkdir -p "$LOG_DIR"
echo -e "${BLUE}===========================================${NC}"
echo -e "${BLUE}情绪博物馆自动化开发环境${NC}"
echo -e "${BLUE}===========================================${NC}"
# 清理函数
cleanup() {
echo -e "\n${YELLOW}正在停止所有服务...${NC}"
if [ -f "$PID_FILE" ]; then
while read line; do
if [ ! -z "$line" ]; then
echo "停止进程: $line"
kill -9 $line 2>/dev/null || true
fi
done < "$PID_FILE"
rm -f "$PID_FILE"
fi
# 清理Maven进程
pkill -f "mvn.*spring-boot:run" 2>/dev/null || true
echo -e "${GREEN}✅ 清理完成${NC}"
exit 0
}
# 捕获退出信号
trap cleanup SIGINT SIGTERM
# 编译项目
compile_project() {
echo -e "${YELLOW}🔄 编译项目...${NC}"
mvn clean compile -DskipTests -q
if [ $? -eq 0 ]; then
echo -e "${GREEN}✅ 编译成功${NC}"
return 0
else
echo -e "${RED}❌ 编译失败${NC}"
return 1
fi
}
# 启动单个服务
start_service() {
local service=$1
local port=$2
echo -e "${BLUE}🚀 启动 $service (端口: $port)...${NC}"
cd "$PROJECT_ROOT/$service"
# 启动服务并获取PID
nohup mvn spring-boot:run -Dspring-boot.run.jvmArguments="-Dserver.port=$port" \
> "$LOG_DIR/$service.log" 2>&1 &
local pid=$!
echo "$pid" >> "$PID_FILE"
echo -e "${GREEN}$service 已启动 (PID: $pid)${NC}"
cd "$PROJECT_ROOT"
}
# 检查服务健康状态
check_service_health() {
local service=$1
local port=$2
local max_attempts=30
local attempt=0
echo -e "${YELLOW}⏳ 等待 $service 启动...${NC}"
while [ $attempt -lt $max_attempts ]; do
if curl -s "http://localhost:$port/actuator/health" > /dev/null 2>&1; then
echo -e "${GREEN}$service 健康检查通过${NC}"
return 0
fi
attempt=$((attempt + 1))
sleep 2
echo -n "."
done
echo -e "\n${RED}$service 健康检查失败${NC}"
return 1
}
# 监控文件变化
monitor_changes() {
echo -e "${BLUE}👀 开始监控文件变化...${NC}"
echo -e "${YELLOW}按 Ctrl+C 停止监控${NC}"
# 使用fswatch监控文件变化 (需要安装: brew install fswatch)
if command -v fswatch > /dev/null; then
fswatch -o -r --exclude="target" --exclude=".git" --exclude="node_modules" \
--include=".*\\.java$" --include=".*\\.yml$" --include=".*\\.xml$" \
"$PROJECT_ROOT" | while read f; do
echo -e "${YELLOW}🔄 检测到文件变化,重新编译...${NC}"
if compile_project; then
echo -e "${GREEN}📝 代码已更新,DevTools将自动重启服务${NC}"
fi
done
else
# 如果没有fswatch,使用简单的循环检查
echo -e "${YELLOW}⚠️ 建议安装 fswatch 以获得更好的文件监控体验:brew install fswatch${NC}"
while true; do
sleep 5
# 简单的时间戳检查(这里可以根据需要扩展)
echo -e "${BLUE}💓 服务运行中... ($(date))${NC}"
done
fi
}
# 显示服务状态
show_status() {
echo -e "${BLUE}===========================================${NC}"
echo -e "${BLUE}服务状态${NC}"
echo -e "${BLUE}===========================================${NC}"
local ports=(8080 8081 8082 8083 8084 8085 8086 8087)
local i=0
for service in "${SERVICES[@]}"; do
local port=${ports[$i]}
if curl -s "http://localhost:$port/actuator/health" > /dev/null 2>&1; then
echo -e "${GREEN}$service (端口: $port) - 运行中${NC}"
else
echo -e "${RED}$service (端口: $port) - 停止${NC}"
fi
i=$((i + 1))
done
echo -e "${BLUE}===========================================${NC}"
echo -e "${YELLOW}📋 日志文件位置: $LOG_DIR/${NC}"
echo -e "${YELLOW}🌐 API网关: http://localhost:9000${NC}"
echo -e "${YELLOW}📚 API文档: http://localhost:9000/doc.html${NC}"
echo -e "${BLUE}===========================================${NC}"
}
# 主函数
main() {
# 清理之前的进程
cleanup 2>/dev/null || true
# 编译项目
if ! compile_project; then
echo -e "${RED}❌ 编译失败,请检查代码错误${NC}"
exit 1
fi
# 启动服务
echo -e "${BLUE}🚀 启动所有微服务...${NC}"
local ports=(9000 9001 9002 9003 9004 9005 9006 9007)
local i=0
for service in "${SERVICES[@]}"; do
start_service "$service" "${ports[$i]}"
sleep 3 # 给服务一些启动时间
i=$((i + 1))
done
# 等待所有服务启动
sleep 10
# 显示状态
show_status
# 开始监控
monitor_changes
}
# 检查参数
case "${1:-}" in
"status")
show_status
;;
"stop")
cleanup
;;
"logs")
echo -e "${BLUE}📋 实时日志 (按 Ctrl+C 退出):${NC}"
tail -f "$LOG_DIR"/*.log
;;
*)
main
;;
esac
-129
View File
@@ -1,129 +0,0 @@
#!/bin/bash
# 情绪博物馆微服务开发启动脚本
# 适用于本地开发环境,可以直接看到日志输出
# 作者: emotion-museum
# 日期: 2025-07-13
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
echo -e "${BLUE}=========================================="
echo -e "情绪博物馆微服务开发启动脚本"
echo -e "适用于本地开发环境 - 实时日志输出"
echo -e "==========================================${NC}"
# 检查Java环境
if ! command -v java &> /dev/null; then
echo -e "${RED}❌ 错误: 未找到Java环境,请安装JDK 17+${NC}"
exit 1
fi
# 检查Maven环境
if ! command -v mvn &> /dev/null; then
echo -e "${RED}❌ 错误: 未找到Maven环境,请安装Maven 3.6+${NC}"
exit 1
fi
echo -e "${GREEN}✅ Java环境检查通过${NC}"
echo -e "${GREEN}✅ Maven环境检查通过${NC}"
# 显示菜单
show_menu() {
echo ""
echo -e "${CYAN}请选择要启动的服务:${NC}"
echo -e "${YELLOW}1.${NC} 启动网关服务 (emotion-gateway:9000)"
echo -e "${YELLOW}2.${NC} 启动用户服务 (emotion-user:9001)"
echo -e "${YELLOW}3.${NC} 启动AI对话服务 (emotion-ai:9002)"
echo -e "${YELLOW}4.${NC} 启动情绪记录服务 (emotion-record:9003)"
echo -e "${YELLOW}5.${NC} 启动成长课题服务 (emotion-growth:9004)"
echo -e "${YELLOW}6.${NC} 启动地图探索服务 (emotion-explore:9005)"
echo -e "${YELLOW}7.${NC} 启动成就奖励服务 (emotion-reward:9006)"
echo -e "${YELLOW}8.${NC} 启动统计分析服务 (emotion-stats:9007)"
echo -e "${YELLOW}9.${NC} 编译所有项目"
echo -e "${YELLOW}0.${NC} 退出"
echo ""
}
# 编译项目
compile_project() {
echo -e "${BLUE}🔨 开始编译项目...${NC}"
mvn clean compile -DskipTests
if [ $? -eq 0 ]; then
echo -e "${GREEN}✅ 项目编译成功!${NC}"
return 0
else
echo -e "${RED}❌ 项目编译失败!${NC}"
return 1
fi
}
# 启动单个服务
start_service() {
local service_name=$1
local service_port=$2
local service_desc=$3
echo -e "${BLUE}🚀 启动 ${service_desc} (${service_name}:${service_port})...${NC}"
echo -e "${YELLOW}💡 提示: 按 Ctrl+C 停止服务${NC}"
echo -e "${PURPLE}📋 日志输出开始:${NC}"
echo "----------------------------------------"
cd $service_name
mvn spring-boot:run -Dspring-boot.run.jvmArguments="-Dserver.port=$service_port"
cd ..
}
# 主循环
while true; do
show_menu
read -p "请输入选择 (0-9): " choice
case $choice in
1)
start_service "emotion-gateway" 9000 "网关服务"
;;
2)
start_service "emotion-user" 9001 "用户服务"
;;
3)
start_service "emotion-ai" 9002 "AI对话服务"
;;
4)
start_service "emotion-record" 9003 "情绪记录服务"
;;
5)
start_service "emotion-growth" 9004 "成长课题服务"
;;
6)
start_service "emotion-explore" 9005 "地图探索服务"
;;
7)
start_service "emotion-reward" 9006 "成就奖励服务"
;;
8)
start_service "emotion-stats" 9007 "统计分析服务"
;;
9)
compile_project
;;
0)
echo -e "${GREEN}👋 退出开发启动脚本${NC}"
exit 0
;;
*)
echo -e "${RED}❌ 无效选择,请输入 0-9${NC}"
;;
esac
echo ""
echo -e "${YELLOW}按任意键继续...${NC}"
read -n 1
done
@@ -1,234 +0,0 @@
# Emotion WebSocket 模块创建总结
## 概述
成功创建了独立的 `emotion-websocket` 微服务模块,用于实现WebSocket实时聊天功能,支持用户与AI的实时对话。
## 创建的文件结构
```
backend/emotion-websocket/
├── pom.xml # Maven配置文件
├── Dockerfile # Docker构建文件
├── README.md # 模块说明文档
├── src/
│ ├── main/
│ │ ├── java/com/emotionmuseum/websocket/
│ │ │ ├── WebsocketApplication.java # 主启动类
│ │ │ ├── config/
│ │ │ │ ├── WebSocketConfig.java # WebSocket配置
│ │ │ │ └── AsyncConfig.java # 异步配置
│ │ │ ├── controller/
│ │ │ │ ├── ChatWebSocketController.java # WebSocket控制器
│ │ │ │ └── WebSocketTestController.java # REST测试控制器
│ │ │ ├── dto/
│ │ │ │ ├── WebSocketMessage.java # WebSocket消息DTO
│ │ │ │ └── ChatRequest.java # 聊天请求DTO
│ │ │ ├── service/
│ │ │ │ ├── ChatWebSocketService.java # WebSocket服务接口
│ │ │ │ ├── AiChatService.java # AI聊天服务接口
│ │ │ │ └── impl/
│ │ │ │ ├── ChatWebSocketServiceImpl.java # WebSocket服务实现
│ │ │ │ └── AiChatServiceImpl.java # AI聊天服务实现
│ │ │ ├── manager/
│ │ │ │ └── WebSocketSessionManager.java # 会话管理器
│ │ │ ├── feign/
│ │ │ │ └── AiServiceClient.java # AI服务Feign客户端
│ │ │ └── listener/
│ │ │ └── WebSocketEventListener.java # WebSocket事件监听器
│ │ └── resources/
│ │ ├── application.yml # 主配置文件
│ │ ├── application-local.yml # 本地环境配置
│ │ ├── bootstrap.yml # 启动配置
│ │ └── static/
│ │ └── websocket-test.html # WebSocket测试页面
│ └── test/
│ ├── java/com/emotionmuseum/websocket/
│ │ └── WebSocketTestApplication.java # 测试类
│ └── resources/
│ └── application-test.yml # 测试环境配置
```
## 主要功能特性
### 1. WebSocket实时通信
- 基于STOMP协议的WebSocket通信
- 支持SockJS降级处理
- 实时双向消息传输
### 2. 消息类型支持
- **TEXT**: 文本消息
- **TYPING**: 正在输入状态
- **SYSTEM**: 系统消息
- **ERROR**: 错误消息
- **HEARTBEAT**: 心跳检测
- **CONNECTION**: 连接状态
- **AI_THINKING**: AI思考中状态
### 3. 发送者类型
- **USER**: 注册用户
- **GUEST**: 游客用户
- **AI**: AI系统
- **SYSTEM**: 系统
### 4. 会话管理
- 用户会话状态管理
- 在线用户统计
- 会话超时处理
### 5. AI集成
- 通过Feign调用emotion-ai服务
- 异步AI响应处理
- AI回复消息分割发送
## 核心组件说明
### 1. WebSocketConfig
- 配置STOMP消息代理
- 设置WebSocket端点
- 配置跨域访问策略
### 2. ChatWebSocketController
- 处理WebSocket消息映射
- 支持聊天消息发送
- 处理用户连接/断开连接
- 心跳检测处理
### 3. WebSocketSessionManager
- 管理用户会话映射
- 在线用户状态跟踪
- 会话信息存储
### 4. ChatWebSocketService
- WebSocket消息处理核心逻辑
- 消息路由和分发
- AI服务集成调用
### 5. AiServiceClient
- 通过Feign调用emotion-ai服务
- 支持用户聊天和游客聊天接口
## 配置说明
### 服务配置
- **端口**: 19007
- **服务名**: emotion-websocket
- **WebSocket端点**: `/ws/chat`
### 消息端点
- **发送消息**: `/app/chat.send`
- **用户连接**: `/app/chat.connect`
- **用户断开**: `/app/chat.disconnect`
- **心跳检测**: `/app/chat.heartbeat`
### 订阅端点
- **用户私有消息**: `/user/queue/messages`
- **会话消息**: `/topic/conversation/{conversationId}`
- **广播消息**: `/topic/broadcast`
## 依赖关系
### 内部依赖
- `emotion-common`: 公共组件
- `emotion-ai`: AI服务(通过Feign调用)
### 外部依赖
- Spring Boot WebSocket
- Spring Cloud Alibaba
- Nacos服务发现
- OpenFeign
- MyBatis Plus
- MySQL
- Redis
## 启动方式
### 1. 单独启动
```bash
cd backend/emotion-websocket
mvn spring-boot:run -Dspring-boot.run.profiles=local
```
### 2. 统一启动脚本
```bash
cd backend
./start-services.sh
```
### 3. Docker启动
```bash
cd backend/emotion-websocket
docker build -t emotion-websocket:1.0.0 .
docker run -d -p 19007:19007 emotion-websocket:1.0.0
```
## 测试方法
### 1. 内置测试页面
访问: http://localhost:19007/websocket-test.html
### 2. REST API测试
```bash
# 发送测试消息
curl -X POST "http://localhost:19007/websocket/send?userId=test-user&message=Hello"
# 查看在线用户
curl -X GET "http://localhost:19007/websocket/online-users"
```
### 3. JavaScript客户端测试
使用SockJS和STOMP.js连接WebSocket端点进行测试
## 集成说明
### 1. 与emotion-ai服务集成
- 通过Feign客户端调用AI服务
- 支持异步AI响应处理
- AI回复消息自动分割发送
### 2. 与前端集成
- 提供标准的WebSocket接口
- 支持SockJS降级处理
- 完整的消息格式定义
### 3. 与网关集成
- 通过emotion-gateway统一访问
- 支持负载均衡
- 统一的服务发现
## 监控和日志
### 健康检查
- http://localhost:19007/actuator/health
### 指标监控
- http://localhost:19007/actuator/metrics
- http://localhost:19007/actuator/prometheus
### 日志配置
- 日志文件: `logs/emotion-websocket.log`
- 支持DEBUG级别的WebSocket调试日志
## 后续扩展建议
1. **消息持久化**: 将聊天消息存储到数据库
2. **文件传输**: 支持图片、文件等多媒体消息
3. **群聊功能**: 支持多用户群组聊天
4. **消息加密**: 增加端到端消息加密
5. **消息撤回**: 支持消息撤回功能
6. **在线状态**: 更详细的用户在线状态管理
7. **消息推送**: 集成推送服务支持离线消息推送
## 总结
成功创建了功能完整的WebSocket聊天微服务模块,具备以下优势:
- ✅ 独立的微服务架构
- ✅ 完整的WebSocket实时通信功能
- ✅ 与AI服务的无缝集成
- ✅ 完善的会话管理机制
- ✅ 丰富的消息类型支持
- ✅ 良好的可扩展性和可维护性
- ✅ 完整的测试和文档支持
该模块可以直接用于生产环境,为用户提供流畅的实时聊天体验。
-1
View File
@@ -1 +0,0 @@
target/emotion-ai-1.0.0.jar中没有主清单属性
-1
View File
@@ -1 +0,0 @@
63083
-102
View File
@@ -1,102 +0,0 @@
2025-07-16T09:03:31.799+08:00 WARN 20008 --- [ main] c.a.nacos.client.logging.NacosLogging : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-16T09:03:31.889+08:00 WARN 20008 --- [ main] c.a.nacos.client.logging.NacosLogging : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-16T09:03:32.194+08:00 WARN 20008 --- [ main] c.a.nacos.client.logging.NacosLogging : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-16T09:03:32.196+08:00 INFO 20008 --- [ main] c.alibaba.nacos.client.utils.ParamUtil : [settings] [req-serv] nacos-server port:8848
2025-07-16T09:03:32.196+08:00 INFO 20008 --- [ main] c.alibaba.nacos.client.utils.ParamUtil : [settings] [http-client] connect timeout:1000
2025-07-16T09:03:32.198+08:00 INFO 20008 --- [ main] c.alibaba.nacos.client.utils.ParamUtil : PER_TASK_CONFIG_SIZE: 3000.0
2025-07-16T09:03:32.254+08:00 INFO 20008 --- [ main] c.a.n.p.a.s.c.ClientAuthPluginManager : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.impl.NacosClientAuthServiceImpl success.
2025-07-16T09:03:32.254+08:00 INFO 20008 --- [ main] c.a.n.p.a.s.c.ClientAuthPluginManager : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.ram.RamClientAuthServiceImpl success.
2025-07-16T09:03:32.277+08:00 INFO 20008 --- [ main] c.a.n.c.a.r.identify.CredentialWatcher : null No credential found
2025-07-16 09:03:32 [main] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.0.2)
2025-07-16 09:03:32 [main] INFO [c.a.n.client.config.impl.LocalConfigInfoProcessor] - LOCAL_SNAPSHOT_PATH:/Users/huazhongmin/nacos/config
2025-07-16 09:03:32 [main] INFO [com.alibaba.nacos.common.remote.client] - [RpcClientFactory] create a new rpc client of 90a9ad1e-7bf0-4f56-9d87-36c72dbc1d26_config-0
2025-07-16 09:03:32 [main] INFO [com.alibaba.nacos.common.remote.client] - [90a9ad1e-7bf0-4f56-9d87-36c72dbc1d26_config-0] Register server push request handler:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$$Lambda/0x00000001342dba98
2025-07-16 09:03:32 [main] INFO [com.alibaba.nacos.common.remote.client] - [90a9ad1e-7bf0-4f56-9d87-36c72dbc1d26_config-0] Register server push request handler:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$$Lambda/0x00000001342dbea8
2025-07-16 09:03:32 [main] INFO [com.alibaba.nacos.common.remote.client] - [90a9ad1e-7bf0-4f56-9d87-36c72dbc1d26_config-0] Registry connection listener to current client:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$1
2025-07-16 09:03:32 [main] INFO [com.alibaba.nacos.common.remote.client] - [90a9ad1e-7bf0-4f56-9d87-36c72dbc1d26_config-0] RpcClient init, ServerListFactory = com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$2
2025-07-16 09:03:32 [main] INFO [com.alibaba.nacos.common.remote.client] - [90a9ad1e-7bf0-4f56-9d87-36c72dbc1d26_config-0] Try to connect to server on start up, server: {serverIp = '127.0.0.1', server main port = 8848}
2025-07-16 09:03:32 [main] INFO [c.a.nacos.common.remote.client.grpc.GrpcClient] - grpc client connection server:127.0.0.1 ip,serverPort:9848,grpcTslConfig:{"sslProvider":"OPENSSL","enableTls":false,"mutualAuthEnable":false,"trustAll":false}
2025-07-16 09:03:33 [main] INFO [com.alibaba.nacos.common.remote.client] - [90a9ad1e-7bf0-4f56-9d87-36c72dbc1d26_config-0] Success to connect to server [127.0.0.1:8848] on start up, connectionId = 1752627813368_127.0.0.1_62143
2025-07-16 09:03:33 [com.alibaba.nacos.client.remote.worker] INFO [com.alibaba.nacos.common.remote.client] - [90a9ad1e-7bf0-4f56-9d87-36c72dbc1d26_config-0] Notify connected event to listeners.
2025-07-16 09:03:33 [com.alibaba.nacos.client.remote.worker] INFO [com.alibaba.nacos.client.config.impl.ClientWorker] - [90a9ad1e-7bf0-4f56-9d87-36c72dbc1d26_config-0] Connected,notify listen context...
2025-07-16 09:03:33 [main] INFO [com.alibaba.nacos.common.remote.client] - [90a9ad1e-7bf0-4f56-9d87-36c72dbc1d26_config-0] Register server push request handler:com.alibaba.nacos.common.remote.client.RpcClient$ConnectResetRequestHandler
2025-07-16 09:03:33 [main] INFO [com.alibaba.nacos.common.remote.client] - [90a9ad1e-7bf0-4f56-9d87-36c72dbc1d26_config-0] Register server push request handler:com.alibaba.nacos.common.remote.client.RpcClient$$Lambda/0x000000013443efc8
2025-07-16 09:03:33 [main] INFO [com.alibaba.nacos.client.config.impl.Limiter] - limitTime:5.0
2025-07-16 09:03:33 [main] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-ai] & group[DEFAULT_GROUP]
2025-07-16 09:03:33 [main] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-ai.properties] & group[DEFAULT_GROUP]
2025-07-16 09:03:33 [main] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-ai-local.properties] & group[DEFAULT_GROUP]
2025-07-16 09:03:33 [main] INFO [o.s.c.b.c.PropertySourceBootstrapConfiguration] - Located property source: [BootstrapPropertySource {name='bootstrapProperties-emotion-ai-local.properties,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-emotion-ai.properties,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-emotion-ai,DEFAULT_GROUP'}]
2025-07-16 09:03:33 [main] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-16 09:03:33 [main] INFO [com.emotionmuseum.ai.AiApplication] - The following 1 profile is active: "local"
2025-07-16 09:03:34 [main] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Multiple Spring Data modules found, entering strict repository configuration mode
2025-07-16 09:03:34 [main] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2025-07-16 09:03:34 [main] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Finished Spring Data repository scanning in 7 ms. Found 0 Redis repository interfaces.
2025-07-16 09:03:34 [main] INFO [o.springframework.cloud.context.scope.GenericScope] - BeanFactory id=75ad894c-a831-3fef-8183-57bf629af884
2025-07-16 09:03:35 [main] INFO [o.s.boot.web.embedded.tomcat.TomcatWebServer] - Tomcat initialized with port(s): 19002 (http)
2025-07-16 09:03:35 [main] INFO [org.apache.catalina.core.StandardService] - Starting service [Tomcat]
2025-07-16 09:03:35 [main] INFO [org.apache.catalina.core.StandardEngine] - Starting Servlet engine: [Apache Tomcat/10.1.5]
2025-07-16 09:03:35 [main] INFO [o.a.c.core.ContainerBase.[Tomcat].[localhost].[/]] - Initializing Spring embedded WebApplicationContext
2025-07-16 09:03:35 [main] INFO [o.s.b.w.s.c.ServletWebServerApplicationContext] - Root WebApplicationContext: initialization completed in 1976 ms
2025-07-16 09:03:35 [main] DEBUG [o.s.web.filter.ServerHttpObservationFilter] - Filter 'serverHttpObservationFilter' configured for use
2025-07-16 09:03:36 [main] INFO [com.emotionmuseum.common.config.SnowflakeConfig] - 使用MAC地址生成的机器ID: 669
2025-07-16 09:03:36 [main] INFO [com.emotionmuseum.common.config.SnowflakeConfig] - 雪花算法配置完成,使用机器ID: 669
2025-07-16 09:03:36 [main] INFO [c.emotionmuseum.common.util.SnowflakeIdGenerator] - 雪花算法ID生成器初始化完成,机器ID: 669
2025-07-16 09:03:36 [main] DEBUG [c.b.m.e.spring.MybatisSqlSessionFactoryBean] - Registered plugin: 'com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor@422b8438'
2025-07-16 09:03:36 [main] DEBUG [c.b.m.e.spring.MybatisSqlSessionFactoryBean] - Property 'mapperLocations' was not specified.
_ _ |_ _ _|_. ___ _ | _
| | |\/|_)(_| | |_\ |_)||_|_\
/ |
3.5.3.1
2025-07-16 09:03:37 [main] DEBUG [o.s.w.s.m.m.a.RequestMappingHandlerAdapter] - ControllerAdvice beans: 0 @ModelAttribute, 0 @InitBinder, 1 RequestBodyAdvice, 1 ResponseBodyAdvice
2025-07-16 09:03:37 [main] DEBUG [o.s.w.s.m.m.a.RequestMappingHandlerMapping] - 25 mappings in 'requestMappingHandlerMapping'
2025-07-16 09:03:37 [main] DEBUG [o.s.web.servlet.handler.SimpleUrlHandlerMapping] - Patterns [/webjars/**, /**] in 'resourceHandlerMapping'
2025-07-16 09:03:37 [main] DEBUG [o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver] - ControllerAdvice beans: 0 @ExceptionHandler, 1 ResponseBodyAdvice
2025-07-16 09:03:38 [main] INFO [o.s.b.actuate.endpoint.web.EndpointLinksResolver] - Exposing 4 endpoint(s) beneath base path '/actuator'
2025-07-16 09:03:38 [main] INFO [o.s.boot.web.embedded.tomcat.TomcatWebServer] - Tomcat started on port(s): 19002 (http) with context path ''
2025-07-16 09:03:38 [main] INFO [com.emotionmuseum.ai.AiApplication] - Started AiApplication in 7.184 seconds (process running for 7.854)
2025-07-16 09:03:58 [http-nio-19002-exec-1] INFO [o.a.c.core.ContainerBase.[Tomcat].[localhost].[/]] - Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-07-16 09:03:58 [http-nio-19002-exec-1] INFO [org.springframework.web.servlet.DispatcherServlet] - Initializing Servlet 'dispatcherServlet'
2025-07-16 09:03:58 [http-nio-19002-exec-1] DEBUG [org.springframework.web.servlet.DispatcherServlet] - Detected StandardServletMultipartResolver
2025-07-16 09:03:58 [http-nio-19002-exec-1] DEBUG [org.springframework.web.servlet.DispatcherServlet] - Detected AcceptHeaderLocaleResolver
2025-07-16 09:03:58 [http-nio-19002-exec-1] DEBUG [org.springframework.web.servlet.DispatcherServlet] - Detected FixedThemeResolver
2025-07-16 09:03:58 [http-nio-19002-exec-1] DEBUG [org.springframework.web.servlet.DispatcherServlet] - Detected org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator@eb62a4b
2025-07-16 09:03:58 [http-nio-19002-exec-1] DEBUG [org.springframework.web.servlet.DispatcherServlet] - Detected org.springframework.web.servlet.support.SessionFlashMapManager@323186f9
2025-07-16 09:03:58 [http-nio-19002-exec-1] DEBUG [org.springframework.web.servlet.DispatcherServlet] - enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
2025-07-16 09:03:58 [http-nio-19002-exec-1] INFO [org.springframework.web.servlet.DispatcherServlet] - Completed initialization in 3 ms
2025-07-16 09:03:58 [http-nio-19002-exec-1] DEBUG [org.springframework.web.servlet.DispatcherServlet] - GET "/actuator/health", parameters={}
2025-07-16 09:03:58 [http-nio-19002-exec-1] INFO [com.zaxxer.hikari.HikariDataSource] - HikariPool-1 - Starting...
2025-07-16 09:03:58 [http-nio-19002-exec-1] INFO [com.zaxxer.hikari.pool.HikariPool] - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@41120a95
2025-07-16 09:03:58 [http-nio-19002-exec-1] INFO [com.zaxxer.hikari.HikariDataSource] - HikariPool-1 - Start completed.
2025-07-16 09:03:59 [http-nio-19002-exec-1] DEBUG [o.s.w.s.m.m.annotation.HttpEntityMethodProcessor] - Using 'application/vnd.spring-boot.actuator.v3+json', given [*/*] and supported [application/vnd.spring-boot.actuator.v3+json, application/vnd.spring-boot.actuator.v2+json, application/json]
2025-07-16 09:03:59 [http-nio-19002-exec-1] DEBUG [o.s.w.s.m.m.annotation.HttpEntityMethodProcessor] - Writing [org.springframework.boot.actuate.health.SystemHealth@5be85732]
2025-07-16 09:03:59 [http-nio-19002-exec-1] DEBUG [org.springframework.web.servlet.DispatcherServlet] - Completed 200 OK
2025-07-16 09:04:12 [http-nio-19002-exec-2] DEBUG [org.springframework.web.servlet.DispatcherServlet] - GET "/actuator/health", parameters={}
2025-07-16 09:04:12 [http-nio-19002-exec-2] DEBUG [o.s.w.s.m.m.annotation.HttpEntityMethodProcessor] - Using 'application/vnd.spring-boot.actuator.v3+json', given [*/*] and supported [application/vnd.spring-boot.actuator.v3+json, application/vnd.spring-boot.actuator.v2+json, application/json]
2025-07-16 09:04:12 [http-nio-19002-exec-2] DEBUG [o.s.w.s.m.m.annotation.HttpEntityMethodProcessor] - Writing [org.springframework.boot.actuate.health.SystemHealth@2065d61c]
2025-07-16 09:04:12 [http-nio-19002-exec-2] DEBUG [org.springframework.web.servlet.DispatcherServlet] - Completed 200 OK
2025-07-16 09:04:31 [http-nio-19002-exec-4] DEBUG [org.springframework.web.servlet.DispatcherServlet] - GET "/ai/actuator/health", parameters={}
2025-07-16 09:04:31 [http-nio-19002-exec-4] DEBUG [o.s.web.servlet.handler.SimpleUrlHandlerMapping] - Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]
2025-07-16 09:04:32 [http-nio-19002-exec-4] DEBUG [c.e.common.interceptor.UserContextInterceptor] - 设置用户上下文: userId=guest_1901232820, requestUri=/ai/actuator/health
2025-07-16 09:04:32 [http-nio-19002-exec-4] DEBUG [o.s.w.servlet.resource.ResourceHttpRequestHandler] - Resource not found
2025-07-16 09:04:32 [http-nio-19002-exec-4] DEBUG [c.e.common.interceptor.UserContextInterceptor] - 清除用户上下文: requestUri=/ai/actuator/health
2025-07-16 09:04:32 [http-nio-19002-exec-4] DEBUG [org.springframework.web.servlet.DispatcherServlet] - Completed 404 NOT_FOUND
2025-07-16 09:04:32 [http-nio-19002-exec-4] DEBUG [org.springframework.web.servlet.DispatcherServlet] - "ERROR" dispatch for GET "/error", parameters={}
2025-07-16 09:04:32 [http-nio-19002-exec-4] DEBUG [o.s.w.s.m.m.a.RequestMappingHandlerMapping] - Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2025-07-16 09:04:32 [http-nio-19002-exec-4] DEBUG [o.s.w.s.m.m.annotation.HttpEntityMethodProcessor] - Using 'application/json', given [*/*] and supported [application/json, application/*+json]
2025-07-16 09:04:32 [http-nio-19002-exec-4] DEBUG [o.s.w.s.m.m.annotation.HttpEntityMethodProcessor] - Writing [{timestamp=Wed Jul 16 09:04:32 CST 2025, status=404, error=Not Found, path=/ai/actuator/health}]
2025-07-16 09:04:32 [http-nio-19002-exec-4] DEBUG [org.springframework.web.servlet.DispatcherServlet] - Exiting from "ERROR" dispatch, status 404
2025-07-16 09:46:47 [Thread-1] WARN [c.alibaba.nacos.common.http.HttpClientBeanHolder] - [HttpClientBeanHolder] Start destroying common HttpClient
2025-07-16 09:46:47 [Thread-7] WARN [com.alibaba.nacos.common.notify.NotifyCenter] - [NotifyCenter] Start destroying Publisher
2025-07-16 09:46:47 [Thread-7] WARN [com.alibaba.nacos.common.notify.NotifyCenter] - [NotifyCenter] Destruction of the end
2025-07-16 09:46:47 [Thread-1] WARN [c.alibaba.nacos.common.http.HttpClientBeanHolder] - [HttpClientBeanHolder] Destruction of the end
2025-07-16 09:46:48 [SpringApplicationShutdownHook] INFO [com.zaxxer.hikari.HikariDataSource] - HikariPool-1 - Shutdown initiated...
2025-07-16 09:46:48 [SpringApplicationShutdownHook] INFO [com.zaxxer.hikari.HikariDataSource] - HikariPool-1 - Shutdown completed.
-98
View File
@@ -1,98 +0,0 @@
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< com.emotionmuseum:emotion-ai >--------------------
[INFO] Building emotion-ai 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] >>> spring-boot:3.0.2:run (default-cli) > test-compile @ emotion-ai >>>
[WARNING] The artifact mysql:mysql-connector-java:jar:8.0.33 has been relocated to com.mysql:mysql-connector-j:jar:8.0.33: MySQL Connector/J artifacts moved to reverse-DNS compliant Maven 2+ coordinates.
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ emotion-ai ---
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.10.1:compile (default-compile) @ emotion-ai ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ emotion-ai ---
[INFO] skip non existing resourceDirectory /Users/huazhongmin/peanut/AppleDevelop/EmotionMuseum/backend/emotion-ai/src/test/resources
[INFO]
[INFO] --- compiler:3.10.1:testCompile (default-testCompile) @ emotion-ai ---
[INFO] No sources to compile
[INFO]
[INFO] <<< spring-boot:3.0.2:run (default-cli) < test-compile @ emotion-ai <<<
[INFO]
[INFO]
[INFO] --- spring-boot:3.0.2:run (default-cli) @ emotion-ai ---
[INFO] Attaching agents: []
2025-07-13T08:45:44.867+08:00  WARN 6918 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:44.925+08:00  WARN 6918 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:44.952+08:00  INFO 6918 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2025-07-13T08:45:45.125+08:00  WARN 6918 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:45.126+08:00  INFO 6918 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : [settings] [req-serv] nacos-server port:8848
2025-07-13T08:45:45.126+08:00  INFO 6918 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : [settings] [http-client] connect timeout:1000
2025-07-13T08:45:45.128+08:00  INFO 6918 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : PER_TASK_CONFIG_SIZE: 3000.0
2025-07-13T08:45:45.177+08:00  INFO 6918 --- [ restartedMain] c.a.n.p.a.s.c.ClientAuthPluginManager  : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.impl.NacosClientAuthServiceImpl success.
2025-07-13T08:45:45.177+08:00  INFO 6918 --- [ restartedMain] c.a.n.p.a.s.c.ClientAuthPluginManager  : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.ram.RamClientAuthServiceImpl success.
2025-07-13T08:45:45.196+08:00  INFO 6918 --- [ restartedMain] c.a.n.c.a.r.identify.CredentialWatcher  : null No credential found
2025-07-13 08:45:45 [restartedMain] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
 :: Spring Boot ::   (v3.0.2)
2025-07-13 08:45:45 [restartedMain] INFO [c.a.n.client.config.impl.LocalConfigInfoProcessor] - LOCAL_SNAPSHOT_PATH:/Users/huazhongmin/nacos/config
2025-07-13 08:45:45 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [RpcClientFactory] create a new rpc client of 0ed45851-eb8a-42d7-b72e-31a045327a03_config-0
2025-07-13 08:45:45 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [0ed45851-eb8a-42d7-b72e-31a045327a03_config-0] Register server push request handler:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$$Lambda/0x000000012531c670
2025-07-13 08:45:45 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [0ed45851-eb8a-42d7-b72e-31a045327a03_config-0] Register server push request handler:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$$Lambda/0x000000012531caa0
2025-07-13 08:45:45 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [0ed45851-eb8a-42d7-b72e-31a045327a03_config-0] Registry connection listener to current client:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$1
2025-07-13 08:45:45 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [0ed45851-eb8a-42d7-b72e-31a045327a03_config-0] RpcClient init, ServerListFactory = com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$2
2025-07-13 08:45:45 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [0ed45851-eb8a-42d7-b72e-31a045327a03_config-0] Try to connect to server on start up, server: {serverIp = '127.0.0.1', server main port = 8848}
2025-07-13 08:45:45 [restartedMain] INFO [c.a.nacos.common.remote.client.grpc.GrpcClient] - grpc client connection server:127.0.0.1 ip,serverPort:9848,grpcTslConfig:{"sslProvider":"OPENSSL","enableTls":false,"mutualAuthEnable":false,"trustAll":false}
2025-07-13 08:45:45 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [0ed45851-eb8a-42d7-b72e-31a045327a03_config-0] Success to connect to server [127.0.0.1:8848] on start up, connectionId = 1752367545782_127.0.0.1_51238
2025-07-13 08:45:45 [com.alibaba.nacos.client.remote.worker] INFO [com.alibaba.nacos.common.remote.client] - [0ed45851-eb8a-42d7-b72e-31a045327a03_config-0] Notify connected event to listeners.
2025-07-13 08:45:45 [com.alibaba.nacos.client.remote.worker] INFO [com.alibaba.nacos.client.config.impl.ClientWorker] - [0ed45851-eb8a-42d7-b72e-31a045327a03_config-0] Connected,notify listen context...
2025-07-13 08:45:45 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [0ed45851-eb8a-42d7-b72e-31a045327a03_config-0] Register server push request handler:com.alibaba.nacos.common.remote.client.RpcClient$ConnectResetRequestHandler
2025-07-13 08:45:45 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [0ed45851-eb8a-42d7-b72e-31a045327a03_config-0] Register server push request handler:com.alibaba.nacos.common.remote.client.RpcClient$$Lambda/0x00000001254a7778
2025-07-13 08:45:45 [restartedMain] INFO [com.alibaba.nacos.client.config.impl.Limiter] - limitTime:5.0
2025-07-13 08:45:46 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-ai] & group[DEFAULT_GROUP]
2025-07-13 08:45:46 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-ai.properties] & group[DEFAULT_GROUP]
2025-07-13 08:45:46 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-ai-dev.properties] & group[DEFAULT_GROUP]
2025-07-13 08:45:46 [restartedMain] INFO [o.s.c.b.c.PropertySourceBootstrapConfiguration] - Located property source: [BootstrapPropertySource {name='bootstrapProperties-emotion-ai-dev.properties,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-emotion-ai.properties,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-emotion-ai,DEFAULT_GROUP'}]
2025-07-13 08:45:46 [restartedMain] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13 08:45:46 [restartedMain] INFO [com.emotionmuseum.ai.AiApplication] - The following 1 profile is active: "dev"
2025-07-13 08:45:46 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Multiple Spring Data modules found, entering strict repository configuration mode
2025-07-13 08:45:46 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2025-07-13 08:45:46 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Finished Spring Data repository scanning in 6 ms. Found 0 Redis repository interfaces.
2025-07-13 08:45:46 [restartedMain] INFO [o.springframework.cloud.context.scope.GenericScope] - BeanFactory id=856a198a-0f81-3510-808c-6303647be993
2025-07-13 08:45:47 [restartedMain] INFO [o.s.boot.web.embedded.tomcat.TomcatWebServer] - Tomcat initialized with port(s): 9002 (http)
2025-07-13 08:45:47 [restartedMain] INFO [org.apache.catalina.core.StandardService] - Starting service [Tomcat]
2025-07-13 08:45:47 [restartedMain] INFO [org.apache.catalina.core.StandardEngine] - Starting Servlet engine: [Apache Tomcat/10.1.5]
2025-07-13 08:45:47 [restartedMain] INFO [o.a.c.core.ContainerBase.[Tomcat].[localhost].[/]] - Initializing Spring embedded WebApplicationContext
2025-07-13 08:45:47 [restartedMain] INFO [o.s.b.w.s.c.ServletWebServerApplicationContext] - Root WebApplicationContext: initialization completed in 1428 ms
2025-07-13 08:45:47 [restartedMain] DEBUG [c.b.m.e.spring.MybatisSqlSessionFactoryBean] - Registered plugin: 'com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor@35a5d991'
2025-07-13 08:45:47 [restartedMain] DEBUG [c.b.m.e.spring.MybatisSqlSessionFactoryBean] - Property 'mapperLocations' was not specified.
_ _ |_ _ _|_. ___ _ | _
| | |\/|_)(_| | |_\ |_)||_|_\
/ |
3.5.3.1
2025-07-13 08:45:49 [restartedMain] WARN [o.s.b.d.autoconfigure.OptionalLiveReloadServer] - Unable to start LiveReload server
2025-07-13 08:45:49 [restartedMain] INFO [o.s.b.actuate.endpoint.web.EndpointLinksResolver] - Exposing 4 endpoint(s) beneath base path '/actuator'
2025-07-13 08:45:49 [restartedMain] INFO [o.s.boot.web.embedded.tomcat.TomcatWebServer] - Tomcat started on port(s): 9002 (http) with context path ''
2025-07-13 08:45:49 [restartedMain] INFO [com.emotionmuseum.ai.AiApplication] - Started AiApplication in 4.676 seconds (process running for 5.147)
2025-07-13 08:45:49 [http-nio-9002-exec-1] INFO [o.a.c.core.ContainerBase.[Tomcat].[localhost].[/]] - Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-07-13 08:45:49 [http-nio-9002-exec-1] INFO [org.springframework.web.servlet.DispatcherServlet] - Initializing Servlet 'dispatcherServlet'
2025-07-13 08:45:49 [http-nio-9002-exec-1] INFO [org.springframework.web.servlet.DispatcherServlet] - Completed initialization in 1 ms
2025-07-13 08:45:49 [http-nio-9002-exec-1] INFO [com.zaxxer.hikari.HikariDataSource] - EmotionAiHikariCP - Starting...
2025-07-13 08:45:49 [http-nio-9002-exec-1] INFO [com.zaxxer.hikari.pool.HikariPool] - EmotionAiHikariCP - Added connection com.mysql.cj.jdbc.ConnectionImpl@56486bc4
2025-07-13 08:45:49 [http-nio-9002-exec-1] INFO [com.zaxxer.hikari.HikariDataSource] - EmotionAiHikariCP - Start completed.
2025-07-13 08:49:24 [Thread-10] WARN [com.alibaba.nacos.common.notify.NotifyCenter] - [NotifyCenter] Start destroying Publisher
2025-07-13 08:49:24 [Thread-10] WARN [com.alibaba.nacos.common.notify.NotifyCenter] - [NotifyCenter] Destruction of the end
2025-07-13 08:49:24 [Thread-4] WARN [c.alibaba.nacos.common.http.HttpClientBeanHolder] - [HttpClientBeanHolder] Start destroying common HttpClient
2025-07-13 08:49:24 [Thread-4] WARN [c.alibaba.nacos.common.http.HttpClientBeanHolder] - [HttpClientBeanHolder] Destruction of the end
2025-07-13 08:49:24 [SpringApplicationShutdownHook] INFO [com.zaxxer.hikari.HikariDataSource] - EmotionAiHikariCP - Shutdown initiated...
2025-07-13 08:49:24 [SpringApplicationShutdownHook] INFO [com.zaxxer.hikari.HikariDataSource] - EmotionAiHikariCP - Shutdown completed.
-120
View File
@@ -1,120 +0,0 @@
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------< com.emotionmuseum:emotion-explore >------------------
[INFO] Building emotion-explore 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] >>> spring-boot:3.0.2:run (default-cli) > test-compile @ emotion-explore >>>
[WARNING] The artifact mysql:mysql-connector-java:jar:8.0.33 has been relocated to com.mysql:mysql-connector-j:jar:8.0.33: MySQL Connector/J artifacts moved to reverse-DNS compliant Maven 2+ coordinates.
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ emotion-explore ---
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.10.1:compile (default-compile) @ emotion-explore ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ emotion-explore ---
[INFO] skip non existing resourceDirectory /Users/huazhongmin/peanut/AppleDevelop/EmotionMuseum/backend/emotion-explore/src/test/resources
[INFO]
[INFO] --- compiler:3.10.1:testCompile (default-testCompile) @ emotion-explore ---
[INFO] No sources to compile
[INFO]
[INFO] <<< spring-boot:3.0.2:run (default-cli) < test-compile @ emotion-explore <<<
[INFO]
[INFO]
[INFO] --- spring-boot:3.0.2:run (default-cli) @ emotion-explore ---
[INFO] Attaching agents: []
2025-07-13T08:45:57.456+08:00  WARN 7147 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:57.542+08:00  WARN 7147 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:57.579+08:00  INFO 7147 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2025-07-13T08:45:57.836+08:00  WARN 7147 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:57.839+08:00  INFO 7147 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : [settings] [req-serv] nacos-server port:8848
2025-07-13T08:45:57.839+08:00  INFO 7147 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : [settings] [http-client] connect timeout:1000
2025-07-13T08:45:57.841+08:00  INFO 7147 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : PER_TASK_CONFIG_SIZE: 3000.0
2025-07-13T08:45:57.909+08:00  INFO 7147 --- [ restartedMain] c.a.n.p.a.s.c.ClientAuthPluginManager  : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.impl.NacosClientAuthServiceImpl success.
2025-07-13T08:45:57.909+08:00  INFO 7147 --- [ restartedMain] c.a.n.p.a.s.c.ClientAuthPluginManager  : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.ram.RamClientAuthServiceImpl success.
2025-07-13T08:45:57.936+08:00  INFO 7147 --- [ restartedMain] c.a.n.c.a.r.identify.CredentialWatcher  : null No credential found
2025-07-13 08:45:58 [restartedMain] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
 :: Spring Boot ::   (v3.0.2)
2025-07-13 08:45:58 [restartedMain] INFO [c.a.n.client.config.impl.LocalConfigInfoProcessor] - LOCAL_SNAPSHOT_PATH:/Users/huazhongmin/nacos/config
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [RpcClientFactory] create a new rpc client of 519c9471-ef87-4204-a56c-74ff37fe3e86_config-0
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [519c9471-ef87-4204-a56c-74ff37fe3e86_config-0] Register server push request handler:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$$Lambda/0x000000012b316a60
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [519c9471-ef87-4204-a56c-74ff37fe3e86_config-0] Register server push request handler:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$$Lambda/0x000000012b316e90
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [519c9471-ef87-4204-a56c-74ff37fe3e86_config-0] Registry connection listener to current client:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$1
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [519c9471-ef87-4204-a56c-74ff37fe3e86_config-0] RpcClient init, ServerListFactory = com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$2
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [519c9471-ef87-4204-a56c-74ff37fe3e86_config-0] Try to connect to server on start up, server: {serverIp = '127.0.0.1', server main port = 8848}
2025-07-13 08:45:58 [restartedMain] INFO [c.a.nacos.common.remote.client.grpc.GrpcClient] - grpc client connection server:127.0.0.1 ip,serverPort:9848,grpcTslConfig:{"sslProvider":"OPENSSL","enableTls":false,"mutualAuthEnable":false,"trustAll":false}
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [519c9471-ef87-4204-a56c-74ff37fe3e86_config-0] Success to connect to server [127.0.0.1:8848] on start up, connectionId = 1752367558691_127.0.0.1_51338
2025-07-13 08:45:58 [com.alibaba.nacos.client.remote.worker] INFO [com.alibaba.nacos.common.remote.client] - [519c9471-ef87-4204-a56c-74ff37fe3e86_config-0] Notify connected event to listeners.
2025-07-13 08:45:58 [com.alibaba.nacos.client.remote.worker] INFO [com.alibaba.nacos.client.config.impl.ClientWorker] - [519c9471-ef87-4204-a56c-74ff37fe3e86_config-0] Connected,notify listen context...
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [519c9471-ef87-4204-a56c-74ff37fe3e86_config-0] Register server push request handler:com.alibaba.nacos.common.remote.client.RpcClient$ConnectResetRequestHandler
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [519c9471-ef87-4204-a56c-74ff37fe3e86_config-0] Register server push request handler:com.alibaba.nacos.common.remote.client.RpcClient$$Lambda/0x000000012b4a4d38
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.client.config.impl.Limiter] - limitTime:5.0
2025-07-13 08:45:58 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-explore] & group[DEFAULT_GROUP]
2025-07-13 08:45:58 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-explore.properties] & group[DEFAULT_GROUP]
2025-07-13 08:45:58 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-explore-dev.properties] & group[DEFAULT_GROUP]
2025-07-13 08:45:58 [restartedMain] INFO [o.s.c.b.c.PropertySourceBootstrapConfiguration] - Located property source: [BootstrapPropertySource {name='bootstrapProperties-emotion-explore-dev.properties,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-emotion-explore.properties,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-emotion-explore,DEFAULT_GROUP'}]
2025-07-13 08:45:58 [restartedMain] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13 08:45:58 [restartedMain] INFO [com.emotionmuseum.explore.ExploreApplication] - The following 1 profile is active: "dev"
2025-07-13 08:45:59 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Multiple Spring Data modules found, entering strict repository configuration mode
2025-07-13 08:45:59 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2025-07-13 08:45:59 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Finished Spring Data repository scanning in 7 ms. Found 0 Redis repository interfaces.
2025-07-13 08:46:00 [restartedMain] WARN [org.mybatis.spring.mapper.ClassPathMapperScanner] - No MyBatis mapper was found in '[com.emotionmuseum.explore.mapper]' package. Please check your configuration.
2025-07-13 08:46:00 [restartedMain] INFO [o.springframework.cloud.context.scope.GenericScope] - BeanFactory id=e3b16e73-732b-3cd7-ba87-b0b05ca7eb72
2025-07-13 08:46:01 [restartedMain] INFO [o.s.boot.web.embedded.tomcat.TomcatWebServer] - Tomcat initialized with port(s): 9005 (http)
2025-07-13 08:46:01 [restartedMain] INFO [org.apache.catalina.core.StandardService] - Starting service [Tomcat]
2025-07-13 08:46:01 [restartedMain] INFO [org.apache.catalina.core.StandardEngine] - Starting Servlet engine: [Apache Tomcat/10.1.5]
2025-07-13 08:46:01 [restartedMain] INFO [o.a.c.core.ContainerBase.[Tomcat].[localhost].[/]] - Initializing Spring embedded WebApplicationContext
2025-07-13 08:46:01 [restartedMain] INFO [o.s.b.w.s.c.ServletWebServerApplicationContext] - Root WebApplicationContext: initialization completed in 2135 ms
2025-07-13 08:46:01 [restartedMain] DEBUG [c.b.m.e.spring.MybatisSqlSessionFactoryBean] - Property 'mapperLocations' was not specified.
_ _ |_ _ _|_. ___ _ | _
| | |\/|_)(_| | |_\ |_)||_|_\
/ |
3.5.3.1
2025-07-13 08:46:02 [restartedMain] WARN [o.s.b.d.autoconfigure.OptionalLiveReloadServer] - Unable to start LiveReload server
2025-07-13 08:46:02 [restartedMain] INFO [o.s.b.actuate.endpoint.web.EndpointLinksResolver] - Exposing 3 endpoint(s) beneath base path '/actuator'
2025-07-13 08:46:02 [restartedMain] WARN [o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext] - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'webServerStartStop'
2025-07-13 08:46:02 [restartedMain] INFO [com.alibaba.druid.pool.DruidDataSource] - {dataSource-0} closing ...
2025-07-13 08:46:02 [restartedMain] INFO [org.apache.catalina.core.StandardService] - Stopping service [Tomcat]
2025-07-13 08:46:02 [restartedMain] INFO [o.s.b.a.logging.ConditionEvaluationReportLogger] -
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2025-07-13 08:46:02 [restartedMain] ERROR [o.s.b.diagnostics.LoggingFailureAnalysisReporter] -
***************************
APPLICATION FAILED TO START
***************************
Description:
Web server failed to start. Port 9005 was already in use.
Action:
Identify and stop the process that's listening on port 9005 or configure this application to listen on another port.
2025-07-13 08:46:02 [Thread-10] WARN [com.alibaba.nacos.common.notify.NotifyCenter] - [NotifyCenter] Start destroying Publisher
2025-07-13 08:46:02 [Thread-10] WARN [com.alibaba.nacos.common.notify.NotifyCenter] - [NotifyCenter] Destruction of the end
2025-07-13 08:46:02 [Thread-4] WARN [c.alibaba.nacos.common.http.HttpClientBeanHolder] - [HttpClientBeanHolder] Start destroying common HttpClient
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10.164 s
[INFO] Finished at: 2025-07-13T08:46:03+08:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:3.0.2:run (default-cli) on project emotion-explore: Process terminated with exit code: 1 -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
-93
View File
@@ -1,93 +0,0 @@
2025-07-16 09:03:40 [main] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.0.2)
2025-07-16 09:03:40 [main] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-16 09:03:40 [main] INFO [com.emotionmuseum.gateway.GatewayApplication] - Starting GatewayApplication using Java 21.0.7 with PID 20154 (/Users/huazhongmin/peanut/AppleDevelop/EmotionMuseum/backend/emotion-gateway/target/emotion-gateway-1.0.0.jar started by huazhongmin in /Users/huazhongmin/peanut/AppleDevelop/EmotionMuseum/backend/emotion-gateway)
2025-07-16 09:03:40 [main] DEBUG [com.emotionmuseum.gateway.GatewayApplication] - Running with Spring Boot v3.0.2, Spring v6.0.4
2025-07-16 09:03:40 [main] INFO [com.emotionmuseum.gateway.GatewayApplication] - The following 1 profile is active: "local"
2025-07-16 09:03:42 [main] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Multiple Spring Data modules found, entering strict repository configuration mode
2025-07-16 09:03:42 [main] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2025-07-16 09:03:42 [main] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Finished Spring Data repository scanning in 13 ms. Found 0 Redis repository interfaces.
2025-07-16 09:03:42 [main] INFO [o.springframework.cloud.context.scope.GenericScope] - BeanFactory id=4710d9c7-5e9d-353b-b960-5b878d180ffe
2025-07-16 09:03:42 [main] INFO [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker] - Bean 'org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration' of type [org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2025-07-16 09:03:42 [main] INFO [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker] - Bean 'org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration$ReactorDeferringLoadBalancerFilterConfig' of type [org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration$ReactorDeferringLoadBalancerFilterConfig] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2025-07-16 09:03:42 [main] INFO [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker] - Bean 'reactorDeferringLoadBalancerExchangeFilterFunction' of type [org.springframework.cloud.client.loadbalancer.reactive.DeferringLoadBalancerExchangeFilterFunction] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2025-07-16 09:03:44 [main] DEBUG [o.s.cloud.gateway.config.GatewayProperties] - Routes supplied from Gateway Properties: [RouteDefinition{id='emotion-user-route', predicates=[PredicateDefinition{name='Path', args={_genkey_0=/user/**}}], filters=[FilterDefinition{name='StripPrefix', args={_genkey_0=0}}], uri=http://localhost:19001, order=0, metadata={}}, RouteDefinition{id='emotion-captcha-route', predicates=[PredicateDefinition{name='Path', args={_genkey_0=/captcha/**}}], filters=[FilterDefinition{name='StripPrefix', args={_genkey_0=0}}], uri=http://localhost:19001, order=0, metadata={}}, RouteDefinition{id='emotion-oauth-route', predicates=[PredicateDefinition{name='Path', args={_genkey_0=/oauth/**}}], filters=[FilterDefinition{name='StripPrefix', args={_genkey_0=0}}], uri=http://localhost:19001, order=0, metadata={}}, RouteDefinition{id='emotion-ai-route', predicates=[PredicateDefinition{name='Path', args={_genkey_0=/ai/**}}], filters=[FilterDefinition{name='StripPrefix', args={_genkey_0=0}}], uri=http://localhost:19002, order=0, metadata={}}]
2025-07-16 09:03:44 [main] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [After]
2025-07-16 09:03:44 [main] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [Before]
2025-07-16 09:03:44 [main] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [Between]
2025-07-16 09:03:44 [main] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [Cookie]
2025-07-16 09:03:44 [main] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [Header]
2025-07-16 09:03:44 [main] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [Host]
2025-07-16 09:03:44 [main] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [Method]
2025-07-16 09:03:44 [main] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [Path]
2025-07-16 09:03:44 [main] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [Query]
2025-07-16 09:03:44 [main] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [ReadBody]
2025-07-16 09:03:44 [main] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [RemoteAddr]
2025-07-16 09:03:44 [main] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [XForwardedRemoteAddr]
2025-07-16 09:03:44 [main] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [Weight]
2025-07-16 09:03:44 [main] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [CloudFoundryRouteService]
2025-07-16 09:03:44 [main] INFO [c.a.c.s.gateway.scg.SentinelSCGAutoConfiguration] - [Sentinel SpringCloudGateway] register SentinelGatewayFilter with order: -2147483648
2025-07-16 09:03:44 [main] DEBUG [o.s.web.reactive.handler.SimpleUrlHandlerMapping] - Patterns [/webjars/**, /**] in 'resourceHandlerMapping'
2025-07-16 09:03:44 [main] INFO [o.s.b.actuate.endpoint.web.EndpointLinksResolver] - Exposing 2 endpoint(s) beneath base path '/actuator'
2025-07-16 09:03:44 [main] DEBUG [o.s.w.r.r.m.annotation.ControllerMethodResolver] - ControllerAdvice beans: none
2025-07-16 09:03:44 [main] INFO [c.a.c.s.gateway.scg.SentinelSCGAutoConfiguration] - [Sentinel SpringCloudGateway] register SentinelGatewayBlockExceptionHandler
2025-07-16 09:03:44 [main] DEBUG [o.s.web.server.adapter.HttpWebHandlerAdapter] - enableLoggingRequestDetails='false': form data and headers will be masked to prevent unsafe logging of potentially sensitive data
2025-07-16 09:03:44 [main] WARN [o.s.c.l.c.LoadBalancerCacheAutoConfiguration$LoadBalancerCaffeineWarnLogger] - Spring Cloud LoadBalancer is currently working with the default cache. While this cache implementation is useful for development and tests, it's recommended to use Caffeine cache in production.You can switch to using Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath.
2025-07-16 09:03:45 [main] INFO [o.s.boot.web.embedded.netty.NettyWebServer] - Netty started on port 19000
2025-07-16 09:03:45 [main] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-user-route applying {_genkey_0=/user/**} to Path
2025-07-16 09:03:45 [main] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-user-route applying filter {_genkey_0=0} to StripPrefix
2025-07-16 09:03:45 [main] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition matched: emotion-user-route
2025-07-16 09:03:45 [main] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-captcha-route applying {_genkey_0=/captcha/**} to Path
2025-07-16 09:03:45 [main] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-captcha-route applying filter {_genkey_0=0} to StripPrefix
2025-07-16 09:03:45 [main] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition matched: emotion-captcha-route
2025-07-16 09:03:45 [main] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-oauth-route applying {_genkey_0=/oauth/**} to Path
2025-07-16 09:03:45 [main] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-oauth-route applying filter {_genkey_0=0} to StripPrefix
2025-07-16 09:03:45 [main] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition matched: emotion-oauth-route
2025-07-16 09:03:45 [main] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-ai-route applying {_genkey_0=/ai/**} to Path
2025-07-16 09:03:45 [main] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-ai-route applying filter {_genkey_0=0} to StripPrefix
2025-07-16 09:03:45 [main] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition matched: emotion-ai-route
2025-07-16 09:03:45 [main] DEBUG [o.s.cloud.gateway.filter.GatewayMetricsFilter] - New routes count: 4
2025-07-16 09:03:45 [main] INFO [com.emotionmuseum.gateway.GatewayApplication] - Started GatewayApplication in 5.105 seconds (process running for 5.704)
2025-07-16 09:03:59 [reactor-http-nio-2] DEBUG [o.s.web.server.adapter.HttpWebHandlerAdapter] - [42be3651-1] HTTP GET "/actuator/health"
INFO: Sentinel log output type is: file
INFO: Sentinel log charset is: utf-8
INFO: Sentinel log base directory is: /Users/huazhongmin/logs/csp/
INFO: Sentinel log name use pid is: false
INFO: Sentinel log level is: INFO
2025-07-16 09:03:59 [lettuce-nioEventLoop-5-1] DEBUG [o.s.w.r.r.m.annotation.ResponseEntityResultHandler] - [42be3651-1] Using 'application/vnd.spring-boot.actuator.v3+json' given [*/*] and supported [application/vnd.spring-boot.actuator.v3+json, application/vnd.spring-boot.actuator.v2+json, application/json]
2025-07-16 09:03:59 [lettuce-nioEventLoop-5-1] DEBUG [o.s.w.r.r.m.annotation.ResponseEntityResultHandler] - [42be3651-1] 0..1 [org.springframework.boot.actuate.health.SystemHealth]
2025-07-16 09:03:59 [lettuce-nioEventLoop-5-1] DEBUG [org.springframework.web.HttpLogging] - [42be3651-1] Encoding [org.springframework.boot.actuate.health.SystemHealth@6fdce5a3]
2025-07-16 09:03:59 [reactor-http-nio-2] DEBUG [o.s.web.server.adapter.HttpWebHandlerAdapter] - [42be3651-1] Completed 200 OK
2025-07-16 09:04:12 [reactor-http-nio-3] DEBUG [o.s.web.server.adapter.HttpWebHandlerAdapter] - [127c1f50-2] HTTP GET "/actuator/health"
2025-07-16 09:04:12 [lettuce-nioEventLoop-5-1] DEBUG [o.s.w.r.r.m.annotation.ResponseEntityResultHandler] - [127c1f50-2] Using 'application/vnd.spring-boot.actuator.v3+json' given [*/*] and supported [application/vnd.spring-boot.actuator.v3+json, application/vnd.spring-boot.actuator.v2+json, application/json]
2025-07-16 09:04:12 [lettuce-nioEventLoop-5-1] DEBUG [o.s.w.r.r.m.annotation.ResponseEntityResultHandler] - [127c1f50-2] 0..1 [org.springframework.boot.actuate.health.SystemHealth]
2025-07-16 09:04:12 [lettuce-nioEventLoop-5-1] DEBUG [org.springframework.web.HttpLogging] - [127c1f50-2] Encoding [org.springframework.boot.actuate.health.SystemHealth@3fb8c67f]
2025-07-16 09:04:12 [reactor-http-nio-3] DEBUG [o.s.web.server.adapter.HttpWebHandlerAdapter] - [127c1f50-2] Completed 200 OK
2025-07-16 09:04:22 [reactor-http-nio-4] DEBUG [o.s.web.server.adapter.HttpWebHandlerAdapter] - [7b8bca8e-3] HTTP GET "/user/actuator/health"
2025-07-16 09:04:22 [reactor-http-nio-4] DEBUG [o.s.c.gateway.handler.RoutePredicateHandlerMapping] - Route matched: emotion-user-route
2025-07-16 09:04:22 [reactor-http-nio-4] DEBUG [o.s.c.gateway.handler.RoutePredicateHandlerMapping] - Mapping [Exchange: GET http://localhost:19000/user/actuator/health] to Route{id='emotion-user-route', uri=http://localhost:19001, order=0, predicate=Paths: [/user/**], match trailing slash: true, gatewayFilters=[[[StripPrefix parts = 0], order = 1]], metadata={}}
2025-07-16 09:04:22 [reactor-http-nio-4] DEBUG [o.s.c.gateway.handler.RoutePredicateHandlerMapping] - [7b8bca8e-3] Mapped to org.springframework.cloud.gateway.handler.FilteringWebHandler@373b5ee9
2025-07-16 09:04:22 [reactor-http-nio-4] DEBUG [o.s.cloud.gateway.handler.FilteringWebHandler] - Sorted gatewayFilterFactories: [[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter@451f87af}, order = -2147483648], [GatewayFilterAdapter{delegate=com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter@3051e0b2}, order = -2147483648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter@4dafba3e}, order = -2147482648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyWriteResponseFilter@287f7811}, order = -1], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardPathFilter@17271176}, order = 0], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.GatewayMetricsFilter@2e34384c}, order = 0], [[StripPrefix parts = 0], order = 1], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter@2b556bb2}, order = 10000], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter@57b75756}, order = 10150], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.LoadBalancerServiceInstanceCookieFilter@5327a06e}, order = 10151], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.WebsocketRoutingFilter@2e3cdec2}, order = 2147483646], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyRoutingFilter@2679311f}, order = 2147483647], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardRoutingFilter@44cb460e}, order = 2147483647]]
2025-07-16 09:04:22 [reactor-http-nio-4] DEBUG [o.s.c.g.f.h.o.ObservedRequestHttpHeadersFilter] - Will instrument the HTTP request headers [Host:"localhost:19000", User-Agent:"curl/8.7.1", Accept:"*/*", Forwarded:"proto=http;host="localhost:19000";for="[0:0:0:0:0:0:0:1]:62291"", X-Forwarded-For:"0:0:0:0:0:0:0:1", X-Forwarded-Proto:"http", X-Forwarded-Port:"19000", X-Forwarded-Host:"localhost:19000"]
2025-07-16 09:04:22 [reactor-http-nio-4] DEBUG [o.s.c.g.f.h.o.ObservedRequestHttpHeadersFilter] - Client observation {name=http.client.requests(null), error=null, context=name='http.client.requests', contextualName='null', error='null', lowCardinalityKeyValues=[http.method='GET', http.status_code='UNKNOWN', spring.cloud.gateway.route.id='emotion-user-route', spring.cloud.gateway.route.uri='http://localhost:19001'], highCardinalityKeyValues=[http.uri='http://localhost:19000/user/actuator/health'], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@4443fec4', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=4.93244E-4, duration(nanos)=493244.0, startTimeNanos=302639138304957}'], parentObservation={name=http.server.requests(null), error=null, context=name='http.server.requests', contextualName='null', error='null', lowCardinalityKeyValues=[exception='none', method='GET', outcome='SUCCESS', status='200', uri='UNKNOWN'], highCardinalityKeyValues=[http.url='/user/actuator/health'], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@62ceb8d8', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.131310068, duration(nanos)=1.31310068E8, startTimeNanos=302639007609097}'], parentObservation=null}} created for the request. New headers are [Host:"localhost:19000", User-Agent:"curl/8.7.1", Accept:"*/*", Forwarded:"proto=http;host="localhost:19000";for="[0:0:0:0:0:0:0:1]:62291"", X-Forwarded-For:"0:0:0:0:0:0:0:1", X-Forwarded-Proto:"http", X-Forwarded-Port:"19000", X-Forwarded-Host:"localhost:19000"]
2025-07-16 09:04:22 [reactor-http-nio-4] DEBUG [o.s.c.g.f.h.o.ObservedResponseHttpHeadersFilter] - Will instrument the response
2025-07-16 09:04:22 [reactor-http-nio-4] DEBUG [o.s.c.g.f.h.o.ObservedResponseHttpHeadersFilter] - The response was handled for observation {name=http.client.requests(null), error=null, context=name='http.client.requests', contextualName='null', error='null', lowCardinalityKeyValues=[http.method='GET', http.status_code='UNKNOWN', spring.cloud.gateway.route.id='emotion-user-route', spring.cloud.gateway.route.uri='http://localhost:19001'], highCardinalityKeyValues=[http.uri='http://localhost:19000/user/actuator/health'], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@4443fec4', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.078344915, duration(nanos)=7.8344915E7, startTimeNanos=302639138304957}'], parentObservation={name=http.server.requests(null), error=null, context=name='http.server.requests', contextualName='null', error='null', lowCardinalityKeyValues=[exception='none', method='GET', outcome='SUCCESS', status='200', uri='UNKNOWN'], highCardinalityKeyValues=[http.url='/user/actuator/health'], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@62ceb8d8', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.209234105, duration(nanos)=2.09234105E8, startTimeNanos=302639007609097}'], parentObservation=null}}
2025-07-16 09:04:22 [reactor-http-nio-4] DEBUG [o.s.web.server.adapter.HttpWebHandlerAdapter] - [7b8bca8e-3] Completed 403 FORBIDDEN
2025-07-16 09:04:31 [reactor-http-nio-5] DEBUG [o.s.web.server.adapter.HttpWebHandlerAdapter] - [37305ece-4] HTTP GET "/ai/actuator/health"
2025-07-16 09:04:31 [reactor-http-nio-5] DEBUG [o.s.c.gateway.handler.RoutePredicateHandlerMapping] - Route matched: emotion-ai-route
2025-07-16 09:04:31 [reactor-http-nio-5] DEBUG [o.s.c.gateway.handler.RoutePredicateHandlerMapping] - Mapping [Exchange: GET http://localhost:19000/ai/actuator/health] to Route{id='emotion-ai-route', uri=http://localhost:19002, order=0, predicate=Paths: [/ai/**], match trailing slash: true, gatewayFilters=[[[StripPrefix parts = 0], order = 1]], metadata={}}
2025-07-16 09:04:31 [reactor-http-nio-5] DEBUG [o.s.c.gateway.handler.RoutePredicateHandlerMapping] - [37305ece-4] Mapped to org.springframework.cloud.gateway.handler.FilteringWebHandler@373b5ee9
2025-07-16 09:04:31 [reactor-http-nio-5] DEBUG [o.s.cloud.gateway.handler.FilteringWebHandler] - Sorted gatewayFilterFactories: [[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter@451f87af}, order = -2147483648], [GatewayFilterAdapter{delegate=com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter@3051e0b2}, order = -2147483648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter@4dafba3e}, order = -2147482648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyWriteResponseFilter@287f7811}, order = -1], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardPathFilter@17271176}, order = 0], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.GatewayMetricsFilter@2e34384c}, order = 0], [[StripPrefix parts = 0], order = 1], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter@2b556bb2}, order = 10000], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter@57b75756}, order = 10150], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.LoadBalancerServiceInstanceCookieFilter@5327a06e}, order = 10151], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.WebsocketRoutingFilter@2e3cdec2}, order = 2147483646], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyRoutingFilter@2679311f}, order = 2147483647], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardRoutingFilter@44cb460e}, order = 2147483647]]
2025-07-16 09:04:31 [reactor-http-nio-5] DEBUG [o.s.c.g.f.h.o.ObservedRequestHttpHeadersFilter] - Will instrument the HTTP request headers [Host:"localhost:19000", User-Agent:"curl/8.7.1", Accept:"*/*", Forwarded:"proto=http;host="localhost:19000";for="[0:0:0:0:0:0:0:1]:62323"", X-Forwarded-For:"0:0:0:0:0:0:0:1", X-Forwarded-Proto:"http", X-Forwarded-Port:"19000", X-Forwarded-Host:"localhost:19000"]
2025-07-16 09:04:31 [reactor-http-nio-5] DEBUG [o.s.c.g.f.h.o.ObservedRequestHttpHeadersFilter] - Client observation {name=http.client.requests(null), error=null, context=name='http.client.requests', contextualName='null', error='null', lowCardinalityKeyValues=[http.method='GET', http.status_code='UNKNOWN', spring.cloud.gateway.route.id='emotion-ai-route', spring.cloud.gateway.route.uri='http://localhost:19002'], highCardinalityKeyValues=[http.uri='http://localhost:19000/ai/actuator/health'], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@14138b12', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=1.69279E-4, duration(nanos)=169279.0, startTimeNanos=302648957485505}'], parentObservation={name=http.server.requests(null), error=null, context=name='http.server.requests', contextualName='null', error='null', lowCardinalityKeyValues=[exception='none', method='GET', outcome='SUCCESS', status='200', uri='UNKNOWN'], highCardinalityKeyValues=[http.url='/ai/actuator/health'], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@3973cc2a', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.003046764, duration(nanos)=3046764.0, startTimeNanos=302648954707166}'], parentObservation=null}} created for the request. New headers are [Host:"localhost:19000", User-Agent:"curl/8.7.1", Accept:"*/*", Forwarded:"proto=http;host="localhost:19000";for="[0:0:0:0:0:0:0:1]:62323"", X-Forwarded-For:"0:0:0:0:0:0:0:1", X-Forwarded-Proto:"http", X-Forwarded-Port:"19000", X-Forwarded-Host:"localhost:19000"]
2025-07-16 09:04:32 [reactor-http-nio-5] DEBUG [o.s.c.g.f.h.o.ObservedResponseHttpHeadersFilter] - Will instrument the response
2025-07-16 09:04:32 [reactor-http-nio-5] DEBUG [o.s.c.g.f.h.o.ObservedResponseHttpHeadersFilter] - The response was handled for observation {name=http.client.requests(null), error=null, context=name='http.client.requests', contextualName='null', error='null', lowCardinalityKeyValues=[http.method='GET', http.status_code='UNKNOWN', spring.cloud.gateway.route.id='emotion-ai-route', spring.cloud.gateway.route.uri='http://localhost:19002'], highCardinalityKeyValues=[http.uri='http://localhost:19000/ai/actuator/health'], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@14138b12', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.078077624, duration(nanos)=7.8077624E7, startTimeNanos=302648957485505}'], parentObservation={name=http.server.requests(null), error=null, context=name='http.server.requests', contextualName='null', error='null', lowCardinalityKeyValues=[exception='none', method='GET', outcome='SUCCESS', status='200', uri='UNKNOWN'], highCardinalityKeyValues=[http.url='/ai/actuator/health'], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@3973cc2a', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.080973745, duration(nanos)=8.0973745E7, startTimeNanos=302648954707166}'], parentObservation=null}}
2025-07-16 09:04:32 [reactor-http-nio-5] DEBUG [o.s.web.server.adapter.HttpWebHandlerAdapter] - [37305ece-4] Completed 404 NOT_FOUND
-98
View File
@@ -1,98 +0,0 @@
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------< com.emotionmuseum:emotion-gateway >------------------
[INFO] Building emotion-gateway 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] >>> spring-boot:3.0.2:run (default-cli) > test-compile @ emotion-gateway >>>
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ emotion-gateway ---
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.10.1:compile (default-compile) @ emotion-gateway ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ emotion-gateway ---
[INFO] skip non existing resourceDirectory /Users/huazhongmin/peanut/AppleDevelop/EmotionMuseum/backend/emotion-gateway/src/test/resources
[INFO]
[INFO] --- compiler:3.10.1:testCompile (default-testCompile) @ emotion-gateway ---
[INFO] No sources to compile
[INFO]
[INFO] <<< spring-boot:3.0.2:run (default-cli) < test-compile @ emotion-gateway <<<
[INFO]
[INFO]
[INFO] --- spring-boot:3.0.2:run (default-cli) @ emotion-gateway ---
[INFO] Attaching agents: []
2025-07-13 08:45:28 [restartedMain] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
 :: Spring Boot ::   (v3.0.2)
2025-07-13 08:45:28 [restartedMain] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13 08:45:28 [restartedMain] INFO [com.emotionmuseum.gateway.GatewayApplication] - Starting GatewayApplication using Java 23.0.2 with PID 6683 (/Users/huazhongmin/peanut/AppleDevelop/EmotionMuseum/backend/emotion-gateway/target/classes started by huazhongmin in /Users/huazhongmin/peanut/AppleDevelop/EmotionMuseum/backend/emotion-gateway)
2025-07-13 08:45:28 [restartedMain] DEBUG [com.emotionmuseum.gateway.GatewayApplication] - Running with Spring Boot v3.0.2, Spring v6.0.4
2025-07-13 08:45:28 [restartedMain] INFO [com.emotionmuseum.gateway.GatewayApplication] - The following 1 profile is active: "dev"
2025-07-13 08:45:28 [restartedMain] INFO [o.s.b.d.env.DevToolsPropertyDefaultsPostProcessor] - Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2025-07-13 08:45:28 [restartedMain] INFO [o.s.b.d.env.DevToolsPropertyDefaultsPostProcessor] - For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
2025-07-13 08:45:29 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Multiple Spring Data modules found, entering strict repository configuration mode
2025-07-13 08:45:29 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2025-07-13 08:45:29 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Finished Spring Data repository scanning in 9 ms. Found 0 Redis repository interfaces.
2025-07-13 08:45:29 [restartedMain] INFO [o.springframework.cloud.context.scope.GenericScope] - BeanFactory id=52e78852-b349-32d3-9f4b-547073a85453
2025-07-13 08:45:29 [restartedMain] INFO [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker] - Bean 'org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration' of type [org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2025-07-13 08:45:29 [restartedMain] INFO [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker] - Bean 'org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration$ReactorDeferringLoadBalancerFilterConfig' of type [org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration$ReactorDeferringLoadBalancerFilterConfig] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2025-07-13 08:45:29 [restartedMain] INFO [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker] - Bean 'reactorDeferringLoadBalancerExchangeFilterFunction' of type [org.springframework.cloud.client.loadbalancer.reactive.DeferringLoadBalancerExchangeFilterFunction] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2025-07-13 08:45:30 [restartedMain] DEBUG [o.s.cloud.gateway.config.GatewayProperties] - Routes supplied from Gateway Properties: [RouteDefinition{id='emotion-user', predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/user/**}}], filters=[FilterDefinition{name='StripPrefix', args={_genkey_0=2}}], uri=lb://emotion-user, order=0, metadata={}}, RouteDefinition{id='emotion-ai', predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/ai/**}}], filters=[FilterDefinition{name='StripPrefix', args={_genkey_0=2}}], uri=lb://emotion-ai, order=0, metadata={}}, RouteDefinition{id='emotion-record', predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/record/**}}], filters=[FilterDefinition{name='StripPrefix', args={_genkey_0=2}}], uri=lb://emotion-record, order=0, metadata={}}, RouteDefinition{id='emotion-growth', predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/growth/**}}], filters=[FilterDefinition{name='StripPrefix', args={_genkey_0=2}}], uri=lb://emotion-growth, order=0, metadata={}}, RouteDefinition{id='emotion-explore', predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/explore/**}}], filters=[FilterDefinition{name='StripPrefix', args={_genkey_0=2}}], uri=lb://emotion-explore, order=0, metadata={}}, RouteDefinition{id='emotion-reward', predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/reward/**}}], filters=[FilterDefinition{name='StripPrefix', args={_genkey_0=2}}], uri=lb://emotion-reward, order=0, metadata={}}, RouteDefinition{id='emotion-stats', predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/stats/**}}], filters=[FilterDefinition{name='StripPrefix', args={_genkey_0=2}}], uri=lb://emotion-stats, order=0, metadata={}}]
2025-07-13 08:45:30 [restartedMain] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [After]
2025-07-13 08:45:30 [restartedMain] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [Before]
2025-07-13 08:45:30 [restartedMain] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [Between]
2025-07-13 08:45:30 [restartedMain] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [Cookie]
2025-07-13 08:45:30 [restartedMain] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [Header]
2025-07-13 08:45:30 [restartedMain] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [Host]
2025-07-13 08:45:30 [restartedMain] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [Method]
2025-07-13 08:45:30 [restartedMain] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [Path]
2025-07-13 08:45:30 [restartedMain] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [Query]
2025-07-13 08:45:30 [restartedMain] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [ReadBody]
2025-07-13 08:45:30 [restartedMain] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [RemoteAddr]
2025-07-13 08:45:30 [restartedMain] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [XForwardedRemoteAddr]
2025-07-13 08:45:30 [restartedMain] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [Weight]
2025-07-13 08:45:30 [restartedMain] INFO [o.s.c.gateway.route.RouteDefinitionRouteLocator] - Loaded RoutePredicateFactory [CloudFoundryRouteService]
2025-07-13 08:45:30 [restartedMain] INFO [c.a.c.s.gateway.scg.SentinelSCGAutoConfiguration] - [Sentinel SpringCloudGateway] register SentinelGatewayFilter with order: -2147483648
2025-07-13 08:45:31 [restartedMain] INFO [o.s.b.actuate.endpoint.web.EndpointLinksResolver] - Exposing 2 endpoint(s) beneath base path '/actuator'
2025-07-13 08:45:31 [restartedMain] INFO [c.a.c.s.gateway.scg.SentinelSCGAutoConfiguration] - [Sentinel SpringCloudGateway] register SentinelGatewayBlockExceptionHandler
2025-07-13 08:45:31 [restartedMain] WARN [o.s.b.d.autoconfigure.OptionalLiveReloadServer] - Unable to start LiveReload server
2025-07-13 08:45:31 [restartedMain] WARN [o.s.c.l.c.LoadBalancerCacheAutoConfiguration$LoadBalancerCaffeineWarnLogger] - Spring Cloud LoadBalancer is currently working with the default cache. While this cache implementation is useful for development and tests, it's recommended to use Caffeine cache in production.You can switch to using Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath.
2025-07-13 08:45:31 [restartedMain] INFO [o.s.boot.web.embedded.netty.NettyWebServer] - Netty started on port 9000
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-user applying {_genkey_0=/api/user/**} to Path
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-user applying filter {_genkey_0=2} to StripPrefix
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition matched: emotion-user
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-ai applying {_genkey_0=/api/ai/**} to Path
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-ai applying filter {_genkey_0=2} to StripPrefix
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition matched: emotion-ai
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-record applying {_genkey_0=/api/record/**} to Path
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-record applying filter {_genkey_0=2} to StripPrefix
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition matched: emotion-record
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-growth applying {_genkey_0=/api/growth/**} to Path
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-growth applying filter {_genkey_0=2} to StripPrefix
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition matched: emotion-growth
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-explore applying {_genkey_0=/api/explore/**} to Path
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-explore applying filter {_genkey_0=2} to StripPrefix
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition matched: emotion-explore
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-reward applying {_genkey_0=/api/reward/**} to Path
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-reward applying filter {_genkey_0=2} to StripPrefix
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition matched: emotion-reward
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-stats applying {_genkey_0=/api/stats/**} to Path
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition emotion-stats applying filter {_genkey_0=2} to StripPrefix
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.c.gateway.route.RouteDefinitionRouteLocator] - RouteDefinition matched: emotion-stats
2025-07-13 08:45:31 [restartedMain] DEBUG [o.s.cloud.gateway.filter.GatewayMetricsFilter] - New routes count: 7
2025-07-13 08:45:31 [restartedMain] INFO [com.emotionmuseum.gateway.GatewayApplication] - Started GatewayApplication in 3.673 seconds (process running for 4.132)
INFO: Sentinel log output type is: file
INFO: Sentinel log charset is: utf-8
INFO: Sentinel log base directory is: /Users/huazhongmin/logs/csp/
INFO: Sentinel log name use pid is: false
INFO: Sentinel log level is: INFO
-121
View File
@@ -1,121 +0,0 @@
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< com.emotionmuseum:emotion-growth >------------------
[INFO] Building emotion-growth 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] >>> spring-boot:3.0.2:run (default-cli) > test-compile @ emotion-growth >>>
[WARNING] The artifact mysql:mysql-connector-java:jar:8.0.33 has been relocated to com.mysql:mysql-connector-j:jar:8.0.33: MySQL Connector/J artifacts moved to reverse-DNS compliant Maven 2+ coordinates.
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ emotion-growth ---
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.10.1:compile (default-compile) @ emotion-growth ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ emotion-growth ---
[INFO] skip non existing resourceDirectory /Users/huazhongmin/peanut/AppleDevelop/EmotionMuseum/backend/emotion-growth/src/test/resources
[INFO]
[INFO] --- compiler:3.10.1:testCompile (default-testCompile) @ emotion-growth ---
[INFO] No sources to compile
[INFO]
[INFO] <<< spring-boot:3.0.2:run (default-cli) < test-compile @ emotion-growth <<<
[INFO]
[INFO]
[INFO] --- spring-boot:3.0.2:run (default-cli) @ emotion-growth ---
[INFO] Attaching agents: []
2025-07-13T08:45:56.958+08:00  WARN 7144 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:57.046+08:00  WARN 7144 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:57.085+08:00  INFO 7144 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2025-07-13T08:45:57.400+08:00  WARN 7144 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:57.403+08:00  INFO 7144 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : [settings] [req-serv] nacos-server port:8848
2025-07-13T08:45:57.403+08:00  INFO 7144 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : [settings] [http-client] connect timeout:1000
2025-07-13T08:45:57.406+08:00  INFO 7144 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : PER_TASK_CONFIG_SIZE: 3000.0
2025-07-13T08:45:57.468+08:00  INFO 7144 --- [ restartedMain] c.a.n.p.a.s.c.ClientAuthPluginManager  : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.impl.NacosClientAuthServiceImpl success.
2025-07-13T08:45:57.468+08:00  INFO 7144 --- [ restartedMain] c.a.n.p.a.s.c.ClientAuthPluginManager  : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.ram.RamClientAuthServiceImpl success.
2025-07-13T08:45:57.498+08:00  INFO 7144 --- [ restartedMain] c.a.n.c.a.r.identify.CredentialWatcher  : null No credential found
2025-07-13 08:45:57 [restartedMain] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
 :: Spring Boot ::   (v3.0.2)
2025-07-13 08:45:57 [restartedMain] INFO [c.a.n.client.config.impl.LocalConfigInfoProcessor] - LOCAL_SNAPSHOT_PATH:/Users/huazhongmin/nacos/config
2025-07-13 08:45:57 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [RpcClientFactory] create a new rpc client of ae27a187-bd28-4e55-8e9f-a546efcdb1c0_config-0
2025-07-13 08:45:57 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [ae27a187-bd28-4e55-8e9f-a546efcdb1c0_config-0] Register server push request handler:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$$Lambda/0x00000001263173d8
2025-07-13 08:45:57 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [ae27a187-bd28-4e55-8e9f-a546efcdb1c0_config-0] Register server push request handler:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$$Lambda/0x0000000126317808
2025-07-13 08:45:57 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [ae27a187-bd28-4e55-8e9f-a546efcdb1c0_config-0] Registry connection listener to current client:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$1
2025-07-13 08:45:57 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [ae27a187-bd28-4e55-8e9f-a546efcdb1c0_config-0] RpcClient init, ServerListFactory = com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$2
2025-07-13 08:45:57 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [ae27a187-bd28-4e55-8e9f-a546efcdb1c0_config-0] Try to connect to server on start up, server: {serverIp = '127.0.0.1', server main port = 8848}
2025-07-13 08:45:57 [restartedMain] INFO [c.a.nacos.common.remote.client.grpc.GrpcClient] - grpc client connection server:127.0.0.1 ip,serverPort:9848,grpcTslConfig:{"sslProvider":"OPENSSL","enableTls":false,"mutualAuthEnable":false,"trustAll":false}
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [ae27a187-bd28-4e55-8e9f-a546efcdb1c0_config-0] Success to connect to server [127.0.0.1:8848] on start up, connectionId = 1752367558311_127.0.0.1_51335
2025-07-13 08:45:58 [com.alibaba.nacos.client.remote.worker] INFO [com.alibaba.nacos.common.remote.client] - [ae27a187-bd28-4e55-8e9f-a546efcdb1c0_config-0] Notify connected event to listeners.
2025-07-13 08:45:58 [com.alibaba.nacos.client.remote.worker] INFO [com.alibaba.nacos.client.config.impl.ClientWorker] - [ae27a187-bd28-4e55-8e9f-a546efcdb1c0_config-0] Connected,notify listen context...
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [ae27a187-bd28-4e55-8e9f-a546efcdb1c0_config-0] Register server push request handler:com.alibaba.nacos.common.remote.client.RpcClient$ConnectResetRequestHandler
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [ae27a187-bd28-4e55-8e9f-a546efcdb1c0_config-0] Register server push request handler:com.alibaba.nacos.common.remote.client.RpcClient$$Lambda/0x00000001264a5490
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.client.config.impl.Limiter] - limitTime:5.0
2025-07-13 08:45:58 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-growth] & group[DEFAULT_GROUP]
2025-07-13 08:45:58 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-growth.properties] & group[DEFAULT_GROUP]
2025-07-13 08:45:58 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-growth-dev.properties] & group[DEFAULT_GROUP]
2025-07-13 08:45:58 [restartedMain] INFO [o.s.c.b.c.PropertySourceBootstrapConfiguration] - Located property source: [BootstrapPropertySource {name='bootstrapProperties-emotion-growth-dev.properties,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-emotion-growth.properties,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-emotion-growth,DEFAULT_GROUP'}]
2025-07-13 08:45:58 [restartedMain] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13 08:45:58 [restartedMain] INFO [com.emotionmuseum.growth.GrowthApplication] - The following 1 profile is active: "dev"
2025-07-13 08:45:59 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Multiple Spring Data modules found, entering strict repository configuration mode
2025-07-13 08:45:59 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2025-07-13 08:45:59 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Finished Spring Data repository scanning in 7 ms. Found 0 Redis repository interfaces.
2025-07-13 08:45:59 [restartedMain] WARN [org.mybatis.spring.mapper.ClassPathMapperScanner] - No MyBatis mapper was found in '[com.emotionmuseum.growth.mapper]' package. Please check your configuration.
2025-07-13 08:45:59 [restartedMain] INFO [o.springframework.cloud.context.scope.GenericScope] - BeanFactory id=4182ccb9-28af-3c79-b715-9374e44c89a7
2025-07-13 08:46:00 [restartedMain] INFO [o.s.boot.web.embedded.tomcat.TomcatWebServer] - Tomcat initialized with port(s): 9004 (http)
2025-07-13 08:46:00 [restartedMain] INFO [org.apache.catalina.core.StandardService] - Starting service [Tomcat]
2025-07-13 08:46:00 [restartedMain] INFO [org.apache.catalina.core.StandardEngine] - Starting Servlet engine: [Apache Tomcat/10.1.5]
2025-07-13 08:46:00 [restartedMain] INFO [o.a.c.core.ContainerBase.[Tomcat].[localhost].[/]] - Initializing Spring embedded WebApplicationContext
2025-07-13 08:46:00 [restartedMain] INFO [o.s.b.w.s.c.ServletWebServerApplicationContext] - Root WebApplicationContext: initialization completed in 2103 ms
2025-07-13 08:46:01 [restartedMain] DEBUG [c.b.m.e.spring.MybatisSqlSessionFactoryBean] - Property 'mapperLocations' was not specified.
_ _ |_ _ _|_. ___ _ | _
| | |\/|_)(_| | |_\ |_)||_|_\
/ |
3.5.3.1
2025-07-13 08:46:02 [restartedMain] WARN [o.s.b.d.autoconfigure.OptionalLiveReloadServer] - Unable to start LiveReload server
2025-07-13 08:46:02 [restartedMain] INFO [o.s.b.actuate.endpoint.web.EndpointLinksResolver] - Exposing 3 endpoint(s) beneath base path '/actuator'
2025-07-13 08:46:02 [restartedMain] WARN [o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext] - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'webServerStartStop'
2025-07-13 08:46:02 [restartedMain] INFO [com.alibaba.druid.pool.DruidDataSource] - {dataSource-0} closing ...
2025-07-13 08:46:02 [restartedMain] INFO [org.apache.catalina.core.StandardService] - Stopping service [Tomcat]
2025-07-13 08:46:02 [restartedMain] INFO [o.s.b.a.logging.ConditionEvaluationReportLogger] -
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2025-07-13 08:46:02 [restartedMain] ERROR [o.s.b.diagnostics.LoggingFailureAnalysisReporter] -
***************************
APPLICATION FAILED TO START
***************************
Description:
Web server failed to start. Port 9004 was already in use.
Action:
Identify and stop the process that's listening on port 9004 or configure this application to listen on another port.
2025-07-13 08:46:02 [Thread-10] WARN [com.alibaba.nacos.common.notify.NotifyCenter] - [NotifyCenter] Start destroying Publisher
2025-07-13 08:46:02 [Thread-4] WARN [c.alibaba.nacos.common.http.HttpClientBeanHolder] - [HttpClientBeanHolder] Start destroying common HttpClient
2025-07-13 08:46:02 [Thread-10] WARN [com.alibaba.nacos.common.notify.NotifyCenter] - [NotifyCenter] Destruction of the end
2025-07-13 08:46:02 [Thread-4] WARN [c.alibaba.nacos.common.http.HttpClientBeanHolder] - [HttpClientBeanHolder] Destruction of the end
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10.247 s
[INFO] Finished at: 2025-07-13T08:46:02+08:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:3.0.2:run (default-cli) on project emotion-growth: Process terminated with exit code: 1 -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
-121
View File
@@ -1,121 +0,0 @@
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< com.emotionmuseum:emotion-record >------------------
[INFO] Building emotion-record 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] >>> spring-boot:3.0.2:run (default-cli) > test-compile @ emotion-record >>>
[WARNING] The artifact mysql:mysql-connector-java:jar:8.0.33 has been relocated to com.mysql:mysql-connector-j:jar:8.0.33: MySQL Connector/J artifacts moved to reverse-DNS compliant Maven 2+ coordinates.
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ emotion-record ---
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.10.1:compile (default-compile) @ emotion-record ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ emotion-record ---
[INFO] skip non existing resourceDirectory /Users/huazhongmin/peanut/AppleDevelop/EmotionMuseum/backend/emotion-record/src/test/resources
[INFO]
[INFO] --- compiler:3.10.1:testCompile (default-testCompile) @ emotion-record ---
[INFO] No sources to compile
[INFO]
[INFO] <<< spring-boot:3.0.2:run (default-cli) < test-compile @ emotion-record <<<
[INFO]
[INFO]
[INFO] --- spring-boot:3.0.2:run (default-cli) @ emotion-record ---
[INFO] Attaching agents: []
2025-07-13T08:45:56.926+08:00  WARN 7142 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:57.023+08:00  WARN 7142 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:57.063+08:00  INFO 7142 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2025-07-13T08:45:57.373+08:00  WARN 7142 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:57.376+08:00  INFO 7142 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : [settings] [req-serv] nacos-server port:8848
2025-07-13T08:45:57.376+08:00  INFO 7142 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : [settings] [http-client] connect timeout:1000
2025-07-13T08:45:57.381+08:00  INFO 7142 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : PER_TASK_CONFIG_SIZE: 3000.0
2025-07-13T08:45:57.454+08:00  INFO 7142 --- [ restartedMain] c.a.n.p.a.s.c.ClientAuthPluginManager  : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.impl.NacosClientAuthServiceImpl success.
2025-07-13T08:45:57.454+08:00  INFO 7142 --- [ restartedMain] c.a.n.p.a.s.c.ClientAuthPluginManager  : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.ram.RamClientAuthServiceImpl success.
2025-07-13T08:45:57.484+08:00  INFO 7142 --- [ restartedMain] c.a.n.c.a.r.identify.CredentialWatcher  : null No credential found
2025-07-13 08:45:57 [restartedMain] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
 :: Spring Boot ::   (v3.0.2)
2025-07-13 08:45:57 [restartedMain] INFO [c.a.n.client.config.impl.LocalConfigInfoProcessor] - LOCAL_SNAPSHOT_PATH:/Users/huazhongmin/nacos/config
2025-07-13 08:45:57 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [RpcClientFactory] create a new rpc client of b1838b09-f01b-45ad-907d-017e3278f9aa_config-0
2025-07-13 08:45:57 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [b1838b09-f01b-45ad-907d-017e3278f9aa_config-0] Register server push request handler:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$$Lambda/0x0000000133317378
2025-07-13 08:45:57 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [b1838b09-f01b-45ad-907d-017e3278f9aa_config-0] Register server push request handler:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$$Lambda/0x00000001333177a8
2025-07-13 08:45:57 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [b1838b09-f01b-45ad-907d-017e3278f9aa_config-0] Registry connection listener to current client:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$1
2025-07-13 08:45:57 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [b1838b09-f01b-45ad-907d-017e3278f9aa_config-0] RpcClient init, ServerListFactory = com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$2
2025-07-13 08:45:57 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [b1838b09-f01b-45ad-907d-017e3278f9aa_config-0] Try to connect to server on start up, server: {serverIp = '127.0.0.1', server main port = 8848}
2025-07-13 08:45:57 [restartedMain] INFO [c.a.nacos.common.remote.client.grpc.GrpcClient] - grpc client connection server:127.0.0.1 ip,serverPort:9848,grpcTslConfig:{"sslProvider":"OPENSSL","enableTls":false,"mutualAuthEnable":false,"trustAll":false}
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [b1838b09-f01b-45ad-907d-017e3278f9aa_config-0] Success to connect to server [127.0.0.1:8848] on start up, connectionId = 1752367558310_127.0.0.1_51334
2025-07-13 08:45:58 [com.alibaba.nacos.client.remote.worker] INFO [com.alibaba.nacos.common.remote.client] - [b1838b09-f01b-45ad-907d-017e3278f9aa_config-0] Notify connected event to listeners.
2025-07-13 08:45:58 [com.alibaba.nacos.client.remote.worker] INFO [com.alibaba.nacos.client.config.impl.ClientWorker] - [b1838b09-f01b-45ad-907d-017e3278f9aa_config-0] Connected,notify listen context...
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [b1838b09-f01b-45ad-907d-017e3278f9aa_config-0] Register server push request handler:com.alibaba.nacos.common.remote.client.RpcClient$ConnectResetRequestHandler
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [b1838b09-f01b-45ad-907d-017e3278f9aa_config-0] Register server push request handler:com.alibaba.nacos.common.remote.client.RpcClient$$Lambda/0x00000001334a4d38
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.client.config.impl.Limiter] - limitTime:5.0
2025-07-13 08:45:58 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-record] & group[DEFAULT_GROUP]
2025-07-13 08:45:58 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-record.properties] & group[DEFAULT_GROUP]
2025-07-13 08:45:58 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-record-dev.properties] & group[DEFAULT_GROUP]
2025-07-13 08:45:58 [restartedMain] INFO [o.s.c.b.c.PropertySourceBootstrapConfiguration] - Located property source: [BootstrapPropertySource {name='bootstrapProperties-emotion-record-dev.properties,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-emotion-record.properties,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-emotion-record,DEFAULT_GROUP'}]
2025-07-13 08:45:58 [restartedMain] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13 08:45:58 [restartedMain] INFO [com.emotionmuseum.record.RecordApplication] - The following 1 profile is active: "dev"
2025-07-13 08:45:59 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Multiple Spring Data modules found, entering strict repository configuration mode
2025-07-13 08:45:59 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2025-07-13 08:45:59 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Finished Spring Data repository scanning in 9 ms. Found 0 Redis repository interfaces.
2025-07-13 08:45:59 [restartedMain] WARN [org.mybatis.spring.mapper.ClassPathMapperScanner] - No MyBatis mapper was found in '[com.emotionmuseum.record.mapper]' package. Please check your configuration.
2025-07-13 08:45:59 [restartedMain] INFO [o.springframework.cloud.context.scope.GenericScope] - BeanFactory id=5c2334fc-7543-34b7-9dd6-173e5c1ad22d
2025-07-13 08:46:00 [restartedMain] INFO [o.s.boot.web.embedded.tomcat.TomcatWebServer] - Tomcat initialized with port(s): 9003 (http)
2025-07-13 08:46:00 [restartedMain] INFO [org.apache.catalina.core.StandardService] - Starting service [Tomcat]
2025-07-13 08:46:00 [restartedMain] INFO [org.apache.catalina.core.StandardEngine] - Starting Servlet engine: [Apache Tomcat/10.1.5]
2025-07-13 08:46:00 [restartedMain] INFO [o.a.c.core.ContainerBase.[Tomcat].[localhost].[/]] - Initializing Spring embedded WebApplicationContext
2025-07-13 08:46:00 [restartedMain] INFO [o.s.b.w.s.c.ServletWebServerApplicationContext] - Root WebApplicationContext: initialization completed in 2100 ms
2025-07-13 08:46:01 [restartedMain] DEBUG [c.b.m.e.spring.MybatisSqlSessionFactoryBean] - Property 'mapperLocations' was not specified.
_ _ |_ _ _|_. ___ _ | _
| | |\/|_)(_| | |_\ |_)||_|_\
/ |
3.5.3.1
2025-07-13 08:46:02 [restartedMain] WARN [o.s.b.d.autoconfigure.OptionalLiveReloadServer] - Unable to start LiveReload server
2025-07-13 08:46:02 [restartedMain] INFO [o.s.b.actuate.endpoint.web.EndpointLinksResolver] - Exposing 3 endpoint(s) beneath base path '/actuator'
2025-07-13 08:46:02 [restartedMain] WARN [o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext] - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'webServerStartStop'
2025-07-13 08:46:02 [restartedMain] INFO [com.alibaba.druid.pool.DruidDataSource] - {dataSource-0} closing ...
2025-07-13 08:46:02 [restartedMain] INFO [org.apache.catalina.core.StandardService] - Stopping service [Tomcat]
2025-07-13 08:46:02 [restartedMain] INFO [o.s.b.a.logging.ConditionEvaluationReportLogger] -
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2025-07-13 08:46:02 [restartedMain] ERROR [o.s.b.diagnostics.LoggingFailureAnalysisReporter] -
***************************
APPLICATION FAILED TO START
***************************
Description:
Web server failed to start. Port 9003 was already in use.
Action:
Identify and stop the process that's listening on port 9003 or configure this application to listen on another port.
2025-07-13 08:46:02 [Thread-4] WARN [c.alibaba.nacos.common.http.HttpClientBeanHolder] - [HttpClientBeanHolder] Start destroying common HttpClient
2025-07-13 08:46:02 [Thread-10] WARN [com.alibaba.nacos.common.notify.NotifyCenter] - [NotifyCenter] Start destroying Publisher
2025-07-13 08:46:02 [Thread-10] WARN [com.alibaba.nacos.common.notify.NotifyCenter] - [NotifyCenter] Destruction of the end
2025-07-13 08:46:02 [Thread-4] WARN [c.alibaba.nacos.common.http.HttpClientBeanHolder] - [HttpClientBeanHolder] Destruction of the end
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10.743 s
[INFO] Finished at: 2025-07-13T08:46:02+08:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:3.0.2:run (default-cli) on project emotion-record: Process terminated with exit code: 1 -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
-121
View File
@@ -1,121 +0,0 @@
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< com.emotionmuseum:emotion-reward >------------------
[INFO] Building emotion-reward 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] >>> spring-boot:3.0.2:run (default-cli) > test-compile @ emotion-reward >>>
[WARNING] The artifact mysql:mysql-connector-java:jar:8.0.33 has been relocated to com.mysql:mysql-connector-j:jar:8.0.33: MySQL Connector/J artifacts moved to reverse-DNS compliant Maven 2+ coordinates.
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ emotion-reward ---
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.10.1:compile (default-compile) @ emotion-reward ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ emotion-reward ---
[INFO] skip non existing resourceDirectory /Users/huazhongmin/peanut/AppleDevelop/EmotionMuseum/backend/emotion-reward/src/test/resources
[INFO]
[INFO] --- compiler:3.10.1:testCompile (default-testCompile) @ emotion-reward ---
[INFO] No sources to compile
[INFO]
[INFO] <<< spring-boot:3.0.2:run (default-cli) < test-compile @ emotion-reward <<<
[INFO]
[INFO]
[INFO] --- spring-boot:3.0.2:run (default-cli) @ emotion-reward ---
[INFO] Attaching agents: []
2025-07-13T08:45:57.848+08:00  WARN 7150 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:57.929+08:00  WARN 7150 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:57.960+08:00  INFO 7150 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2025-07-13T08:45:58.176+08:00  WARN 7150 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:58.177+08:00  INFO 7150 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : [settings] [req-serv] nacos-server port:8848
2025-07-13T08:45:58.177+08:00  INFO 7150 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : [settings] [http-client] connect timeout:1000
2025-07-13T08:45:58.179+08:00  INFO 7150 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : PER_TASK_CONFIG_SIZE: 3000.0
2025-07-13T08:45:58.235+08:00  INFO 7150 --- [ restartedMain] c.a.n.p.a.s.c.ClientAuthPluginManager  : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.impl.NacosClientAuthServiceImpl success.
2025-07-13T08:45:58.235+08:00  INFO 7150 --- [ restartedMain] c.a.n.p.a.s.c.ClientAuthPluginManager  : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.ram.RamClientAuthServiceImpl success.
2025-07-13T08:45:58.262+08:00  INFO 7150 --- [ restartedMain] c.a.n.c.a.r.identify.CredentialWatcher  : null No credential found
2025-07-13 08:45:58 [restartedMain] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
 :: Spring Boot ::   (v3.0.2)
2025-07-13 08:45:58 [restartedMain] INFO [c.a.n.client.config.impl.LocalConfigInfoProcessor] - LOCAL_SNAPSHOT_PATH:/Users/huazhongmin/nacos/config
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [RpcClientFactory] create a new rpc client of e6c044c5-b7c5-4811-8b89-760db0f4f9ae_config-0
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [e6c044c5-b7c5-4811-8b89-760db0f4f9ae_config-0] Register server push request handler:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$$Lambda/0x0000000126316a60
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [e6c044c5-b7c5-4811-8b89-760db0f4f9ae_config-0] Register server push request handler:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$$Lambda/0x0000000126316e90
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [e6c044c5-b7c5-4811-8b89-760db0f4f9ae_config-0] Registry connection listener to current client:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$1
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [e6c044c5-b7c5-4811-8b89-760db0f4f9ae_config-0] RpcClient init, ServerListFactory = com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$2
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [e6c044c5-b7c5-4811-8b89-760db0f4f9ae_config-0] Try to connect to server on start up, server: {serverIp = '127.0.0.1', server main port = 8848}
2025-07-13 08:45:58 [restartedMain] INFO [c.a.nacos.common.remote.client.grpc.GrpcClient] - grpc client connection server:127.0.0.1 ip,serverPort:9848,grpcTslConfig:{"sslProvider":"OPENSSL","enableTls":false,"mutualAuthEnable":false,"trustAll":false}
2025-07-13 08:45:59 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [e6c044c5-b7c5-4811-8b89-760db0f4f9ae_config-0] Success to connect to server [127.0.0.1:8848] on start up, connectionId = 1752367559041_127.0.0.1_51343
2025-07-13 08:45:59 [com.alibaba.nacos.client.remote.worker] INFO [com.alibaba.nacos.common.remote.client] - [e6c044c5-b7c5-4811-8b89-760db0f4f9ae_config-0] Notify connected event to listeners.
2025-07-13 08:45:59 [com.alibaba.nacos.client.remote.worker] INFO [com.alibaba.nacos.client.config.impl.ClientWorker] - [e6c044c5-b7c5-4811-8b89-760db0f4f9ae_config-0] Connected,notify listen context...
2025-07-13 08:45:59 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [e6c044c5-b7c5-4811-8b89-760db0f4f9ae_config-0] Register server push request handler:com.alibaba.nacos.common.remote.client.RpcClient$ConnectResetRequestHandler
2025-07-13 08:45:59 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [e6c044c5-b7c5-4811-8b89-760db0f4f9ae_config-0] Register server push request handler:com.alibaba.nacos.common.remote.client.RpcClient$$Lambda/0x00000001264a4d38
2025-07-13 08:45:59 [restartedMain] INFO [com.alibaba.nacos.client.config.impl.Limiter] - limitTime:5.0
2025-07-13 08:45:59 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-reward] & group[DEFAULT_GROUP]
2025-07-13 08:45:59 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-reward.properties] & group[DEFAULT_GROUP]
2025-07-13 08:45:59 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-reward-dev.properties] & group[DEFAULT_GROUP]
2025-07-13 08:45:59 [restartedMain] INFO [o.s.c.b.c.PropertySourceBootstrapConfiguration] - Located property source: [BootstrapPropertySource {name='bootstrapProperties-emotion-reward-dev.properties,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-emotion-reward.properties,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-emotion-reward,DEFAULT_GROUP'}]
2025-07-13 08:45:59 [restartedMain] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13 08:45:59 [restartedMain] INFO [com.emotionmuseum.reward.RewardApplication] - The following 1 profile is active: "dev"
2025-07-13 08:46:00 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Multiple Spring Data modules found, entering strict repository configuration mode
2025-07-13 08:46:00 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2025-07-13 08:46:00 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Finished Spring Data repository scanning in 9 ms. Found 0 Redis repository interfaces.
2025-07-13 08:46:00 [restartedMain] WARN [org.mybatis.spring.mapper.ClassPathMapperScanner] - No MyBatis mapper was found in '[com.emotionmuseum.reward.mapper]' package. Please check your configuration.
2025-07-13 08:46:00 [restartedMain] INFO [o.springframework.cloud.context.scope.GenericScope] - BeanFactory id=2ebf993f-e9df-3362-92a0-57f648eeab93
2025-07-13 08:46:01 [restartedMain] INFO [o.s.boot.web.embedded.tomcat.TomcatWebServer] - Tomcat initialized with port(s): 9006 (http)
2025-07-13 08:46:01 [restartedMain] INFO [org.apache.catalina.core.StandardService] - Starting service [Tomcat]
2025-07-13 08:46:01 [restartedMain] INFO [org.apache.catalina.core.StandardEngine] - Starting Servlet engine: [Apache Tomcat/10.1.5]
2025-07-13 08:46:01 [restartedMain] INFO [o.a.c.core.ContainerBase.[Tomcat].[localhost].[/]] - Initializing Spring embedded WebApplicationContext
2025-07-13 08:46:01 [restartedMain] INFO [o.s.b.w.s.c.ServletWebServerApplicationContext] - Root WebApplicationContext: initialization completed in 2137 ms
2025-07-13 08:46:02 [restartedMain] DEBUG [c.b.m.e.spring.MybatisSqlSessionFactoryBean] - Property 'mapperLocations' was not specified.
_ _ |_ _ _|_. ___ _ | _
| | |\/|_)(_| | |_\ |_)||_|_\
/ |
3.5.3.1
2025-07-13 08:46:02 [restartedMain] WARN [o.s.b.d.autoconfigure.OptionalLiveReloadServer] - Unable to start LiveReload server
2025-07-13 08:46:02 [restartedMain] INFO [o.s.b.actuate.endpoint.web.EndpointLinksResolver] - Exposing 3 endpoint(s) beneath base path '/actuator'
2025-07-13 08:46:02 [restartedMain] WARN [o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext] - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'webServerStartStop'
2025-07-13 08:46:02 [restartedMain] INFO [com.alibaba.druid.pool.DruidDataSource] - {dataSource-0} closing ...
2025-07-13 08:46:02 [restartedMain] INFO [org.apache.catalina.core.StandardService] - Stopping service [Tomcat]
2025-07-13 08:46:02 [restartedMain] INFO [o.s.b.a.logging.ConditionEvaluationReportLogger] -
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2025-07-13 08:46:02 [restartedMain] ERROR [o.s.b.diagnostics.LoggingFailureAnalysisReporter] -
***************************
APPLICATION FAILED TO START
***************************
Description:
Web server failed to start. Port 9006 was already in use.
Action:
Identify and stop the process that's listening on port 9006 or configure this application to listen on another port.
2025-07-13 08:46:03 [Thread-10] WARN [com.alibaba.nacos.common.notify.NotifyCenter] - [NotifyCenter] Start destroying Publisher
2025-07-13 08:46:03 [Thread-4] WARN [c.alibaba.nacos.common.http.HttpClientBeanHolder] - [HttpClientBeanHolder] Start destroying common HttpClient
2025-07-13 08:46:03 [Thread-10] WARN [com.alibaba.nacos.common.notify.NotifyCenter] - [NotifyCenter] Destruction of the end
2025-07-13 08:46:03 [Thread-4] WARN [c.alibaba.nacos.common.http.HttpClientBeanHolder] - [HttpClientBeanHolder] Destruction of the end
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 9.965 s
[INFO] Finished at: 2025-07-13T08:46:03+08:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:3.0.2:run (default-cli) on project emotion-reward: Process terminated with exit code: 1 -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
-121
View File
@@ -1,121 +0,0 @@
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< com.emotionmuseum:emotion-stats >-------------------
[INFO] Building emotion-stats 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] >>> spring-boot:3.0.2:run (default-cli) > test-compile @ emotion-stats >>>
[WARNING] The artifact mysql:mysql-connector-java:jar:8.0.33 has been relocated to com.mysql:mysql-connector-j:jar:8.0.33: MySQL Connector/J artifacts moved to reverse-DNS compliant Maven 2+ coordinates.
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ emotion-stats ---
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.10.1:compile (default-compile) @ emotion-stats ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ emotion-stats ---
[INFO] skip non existing resourceDirectory /Users/huazhongmin/peanut/AppleDevelop/EmotionMuseum/backend/emotion-stats/src/test/resources
[INFO]
[INFO] --- compiler:3.10.1:testCompile (default-testCompile) @ emotion-stats ---
[INFO] No sources to compile
[INFO]
[INFO] <<< spring-boot:3.0.2:run (default-cli) < test-compile @ emotion-stats <<<
[INFO]
[INFO]
[INFO] --- spring-boot:3.0.2:run (default-cli) @ emotion-stats ---
[INFO] Attaching agents: []
2025-07-13T08:45:58.270+08:00  WARN 7167 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:58.356+08:00  WARN 7167 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:58.403+08:00  INFO 7167 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2025-07-13T08:45:58.635+08:00  WARN 7167 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:58.637+08:00  INFO 7167 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : [settings] [req-serv] nacos-server port:8848
2025-07-13T08:45:58.637+08:00  INFO 7167 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : [settings] [http-client] connect timeout:1000
2025-07-13T08:45:58.638+08:00  INFO 7167 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : PER_TASK_CONFIG_SIZE: 3000.0
2025-07-13T08:45:58.691+08:00  INFO 7167 --- [ restartedMain] c.a.n.p.a.s.c.ClientAuthPluginManager  : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.impl.NacosClientAuthServiceImpl success.
2025-07-13T08:45:58.691+08:00  INFO 7167 --- [ restartedMain] c.a.n.p.a.s.c.ClientAuthPluginManager  : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.ram.RamClientAuthServiceImpl success.
2025-07-13T08:45:58.712+08:00  INFO 7167 --- [ restartedMain] c.a.n.c.a.r.identify.CredentialWatcher  : null No credential found
2025-07-13 08:45:58 [restartedMain] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
 :: Spring Boot ::   (v3.0.2)
2025-07-13 08:45:58 [restartedMain] INFO [c.a.n.client.config.impl.LocalConfigInfoProcessor] - LOCAL_SNAPSHOT_PATH:/Users/huazhongmin/nacos/config
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [RpcClientFactory] create a new rpc client of 51a04bce-977d-489e-ba5c-8147a68680fb_config-0
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [51a04bce-977d-489e-ba5c-8147a68680fb_config-0] Register server push request handler:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$$Lambda/0x000000012d31bc18
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [51a04bce-977d-489e-ba5c-8147a68680fb_config-0] Register server push request handler:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$$Lambda/0x000000012d31c048
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [51a04bce-977d-489e-ba5c-8147a68680fb_config-0] Registry connection listener to current client:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$1
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [51a04bce-977d-489e-ba5c-8147a68680fb_config-0] RpcClient init, ServerListFactory = com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$2
2025-07-13 08:45:58 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [51a04bce-977d-489e-ba5c-8147a68680fb_config-0] Try to connect to server on start up, server: {serverIp = '127.0.0.1', server main port = 8848}
2025-07-13 08:45:58 [restartedMain] INFO [c.a.nacos.common.remote.client.grpc.GrpcClient] - grpc client connection server:127.0.0.1 ip,serverPort:9848,grpcTslConfig:{"sslProvider":"OPENSSL","enableTls":false,"mutualAuthEnable":false,"trustAll":false}
2025-07-13 08:45:59 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [51a04bce-977d-489e-ba5c-8147a68680fb_config-0] Success to connect to server [127.0.0.1:8848] on start up, connectionId = 1752367559501_127.0.0.1_51348
2025-07-13 08:45:59 [com.alibaba.nacos.client.remote.worker] INFO [com.alibaba.nacos.common.remote.client] - [51a04bce-977d-489e-ba5c-8147a68680fb_config-0] Notify connected event to listeners.
2025-07-13 08:45:59 [com.alibaba.nacos.client.remote.worker] INFO [com.alibaba.nacos.client.config.impl.ClientWorker] - [51a04bce-977d-489e-ba5c-8147a68680fb_config-0] Connected,notify listen context...
2025-07-13 08:45:59 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [51a04bce-977d-489e-ba5c-8147a68680fb_config-0] Register server push request handler:com.alibaba.nacos.common.remote.client.RpcClient$ConnectResetRequestHandler
2025-07-13 08:45:59 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [51a04bce-977d-489e-ba5c-8147a68680fb_config-0] Register server push request handler:com.alibaba.nacos.common.remote.client.RpcClient$$Lambda/0x000000012d4a7d30
2025-07-13 08:45:59 [restartedMain] INFO [com.alibaba.nacos.client.config.impl.Limiter] - limitTime:5.0
2025-07-13 08:45:59 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-stats] & group[DEFAULT_GROUP]
2025-07-13 08:45:59 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-stats.properties] & group[DEFAULT_GROUP]
2025-07-13 08:45:59 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-stats-dev.properties] & group[DEFAULT_GROUP]
2025-07-13 08:45:59 [restartedMain] INFO [o.s.c.b.c.PropertySourceBootstrapConfiguration] - Located property source: [BootstrapPropertySource {name='bootstrapProperties-emotion-stats-dev.properties,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-emotion-stats.properties,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-emotion-stats,DEFAULT_GROUP'}]
2025-07-13 08:45:59 [restartedMain] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13 08:45:59 [restartedMain] INFO [com.emotionmuseum.stats.StatsApplication] - The following 1 profile is active: "dev"
2025-07-13 08:46:00 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Multiple Spring Data modules found, entering strict repository configuration mode
2025-07-13 08:46:00 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2025-07-13 08:46:00 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Finished Spring Data repository scanning in 6 ms. Found 0 Redis repository interfaces.
2025-07-13 08:46:01 [restartedMain] WARN [org.mybatis.spring.mapper.ClassPathMapperScanner] - No MyBatis mapper was found in '[com.emotionmuseum.stats.mapper]' package. Please check your configuration.
2025-07-13 08:46:01 [restartedMain] INFO [o.springframework.cloud.context.scope.GenericScope] - BeanFactory id=af125b37-688d-3aab-bf37-e7a7060f4920
2025-07-13 08:46:01 [restartedMain] INFO [o.s.boot.web.embedded.tomcat.TomcatWebServer] - Tomcat initialized with port(s): 9007 (http)
2025-07-13 08:46:01 [restartedMain] INFO [org.apache.catalina.core.StandardService] - Starting service [Tomcat]
2025-07-13 08:46:01 [restartedMain] INFO [org.apache.catalina.core.StandardEngine] - Starting Servlet engine: [Apache Tomcat/10.1.5]
2025-07-13 08:46:01 [restartedMain] INFO [o.a.c.core.ContainerBase.[Tomcat].[localhost].[/]] - Initializing Spring embedded WebApplicationContext
2025-07-13 08:46:01 [restartedMain] INFO [o.s.b.w.s.c.ServletWebServerApplicationContext] - Root WebApplicationContext: initialization completed in 2077 ms
2025-07-13 08:46:02 [restartedMain] DEBUG [c.b.m.e.spring.MybatisSqlSessionFactoryBean] - Property 'mapperLocations' was not specified.
_ _ |_ _ _|_. ___ _ | _
| | |\/|_)(_| | |_\ |_)||_|_\
/ |
3.5.3.1
2025-07-13 08:46:02 [restartedMain] WARN [o.s.b.d.autoconfigure.OptionalLiveReloadServer] - Unable to start LiveReload server
2025-07-13 08:46:02 [restartedMain] INFO [o.s.b.actuate.endpoint.web.EndpointLinksResolver] - Exposing 3 endpoint(s) beneath base path '/actuator'
2025-07-13 08:46:03 [restartedMain] WARN [o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext] - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'webServerStartStop'
2025-07-13 08:46:03 [restartedMain] INFO [com.alibaba.druid.pool.DruidDataSource] - {dataSource-0} closing ...
2025-07-13 08:46:03 [restartedMain] INFO [org.apache.catalina.core.StandardService] - Stopping service [Tomcat]
2025-07-13 08:46:03 [restartedMain] INFO [o.s.b.a.logging.ConditionEvaluationReportLogger] -
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2025-07-13 08:46:03 [restartedMain] ERROR [o.s.b.diagnostics.LoggingFailureAnalysisReporter] -
***************************
APPLICATION FAILED TO START
***************************
Description:
Web server failed to start. Port 9007 was already in use.
Action:
Identify and stop the process that's listening on port 9007 or configure this application to listen on another port.
2025-07-13 08:46:03 [Thread-4] WARN [c.alibaba.nacos.common.http.HttpClientBeanHolder] - [HttpClientBeanHolder] Start destroying common HttpClient
2025-07-13 08:46:03 [Thread-10] WARN [com.alibaba.nacos.common.notify.NotifyCenter] - [NotifyCenter] Start destroying Publisher
2025-07-13 08:46:03 [Thread-10] WARN [com.alibaba.nacos.common.notify.NotifyCenter] - [NotifyCenter] Destruction of the end
2025-07-13 08:46:03 [Thread-4] WARN [c.alibaba.nacos.common.http.HttpClientBeanHolder] - [HttpClientBeanHolder] Destruction of the end
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 9.734 s
[INFO] Finished at: 2025-07-13T08:46:03+08:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:3.0.2:run (default-cli) on project emotion-stats: Process terminated with exit code: 1 -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
-125
View File
@@ -1,125 +0,0 @@
2025-07-16T09:03:18.888+08:00 WARN 19784 --- [ main] c.a.nacos.client.logging.NacosLogging : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-16T09:03:18.983+08:00 WARN 19784 --- [ main] c.a.nacos.client.logging.NacosLogging : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-16T09:03:19.261+08:00 WARN 19784 --- [ main] c.a.nacos.client.logging.NacosLogging : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-16T09:03:19.262+08:00 INFO 19784 --- [ main] c.alibaba.nacos.client.utils.ParamUtil : [settings] [req-serv] nacos-server port:8848
2025-07-16T09:03:19.263+08:00 INFO 19784 --- [ main] c.alibaba.nacos.client.utils.ParamUtil : [settings] [http-client] connect timeout:1000
2025-07-16T09:03:19.265+08:00 INFO 19784 --- [ main] c.alibaba.nacos.client.utils.ParamUtil : PER_TASK_CONFIG_SIZE: 3000.0
2025-07-16T09:03:19.321+08:00 INFO 19784 --- [ main] c.a.n.p.a.s.c.ClientAuthPluginManager : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.impl.NacosClientAuthServiceImpl success.
2025-07-16T09:03:19.321+08:00 INFO 19784 --- [ main] c.a.n.p.a.s.c.ClientAuthPluginManager : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.ram.RamClientAuthServiceImpl success.
2025-07-16T09:03:19.345+08:00 INFO 19784 --- [ main] c.a.n.c.a.r.identify.CredentialWatcher : null No credential found
2025-07-16 09:03:19 [main] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.0.2)
2025-07-16 09:03:19 [main] INFO [c.a.n.client.config.impl.LocalConfigInfoProcessor] - LOCAL_SNAPSHOT_PATH:/Users/huazhongmin/nacos/config
2025-07-16 09:03:19 [main] INFO [com.alibaba.nacos.common.remote.client] - [RpcClientFactory] create a new rpc client of 745c3cf4-7cb2-4ac6-a11a-86fd37de7293_config-0
2025-07-16 09:03:19 [main] INFO [com.alibaba.nacos.common.remote.client] - [745c3cf4-7cb2-4ac6-a11a-86fd37de7293_config-0] Register server push request handler:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$$Lambda/0x000000013d2dc530
2025-07-16 09:03:19 [main] INFO [com.alibaba.nacos.common.remote.client] - [745c3cf4-7cb2-4ac6-a11a-86fd37de7293_config-0] Register server push request handler:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$$Lambda/0x000000013d2dc940
2025-07-16 09:03:19 [main] INFO [com.alibaba.nacos.common.remote.client] - [745c3cf4-7cb2-4ac6-a11a-86fd37de7293_config-0] Registry connection listener to current client:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$1
2025-07-16 09:03:19 [main] INFO [com.alibaba.nacos.common.remote.client] - [745c3cf4-7cb2-4ac6-a11a-86fd37de7293_config-0] RpcClient init, ServerListFactory = com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$2
2025-07-16 09:03:19 [main] INFO [com.alibaba.nacos.common.remote.client] - [745c3cf4-7cb2-4ac6-a11a-86fd37de7293_config-0] Try to connect to server on start up, server: {serverIp = '127.0.0.1', server main port = 8848}
2025-07-16 09:03:19 [main] INFO [c.a.nacos.common.remote.client.grpc.GrpcClient] - grpc client connection server:127.0.0.1 ip,serverPort:9848,grpcTslConfig:{"sslProvider":"OPENSSL","enableTls":false,"mutualAuthEnable":false,"trustAll":false}
2025-07-16 09:03:20 [main] INFO [com.alibaba.nacos.common.remote.client] - [745c3cf4-7cb2-4ac6-a11a-86fd37de7293_config-0] Success to connect to server [127.0.0.1:8848] on start up, connectionId = 1752627800162_127.0.0.1_62095
2025-07-16 09:03:20 [com.alibaba.nacos.client.remote.worker] INFO [com.alibaba.nacos.common.remote.client] - [745c3cf4-7cb2-4ac6-a11a-86fd37de7293_config-0] Notify connected event to listeners.
2025-07-16 09:03:20 [com.alibaba.nacos.client.remote.worker] INFO [com.alibaba.nacos.client.config.impl.ClientWorker] - [745c3cf4-7cb2-4ac6-a11a-86fd37de7293_config-0] Connected,notify listen context...
2025-07-16 09:03:20 [main] INFO [com.alibaba.nacos.common.remote.client] - [745c3cf4-7cb2-4ac6-a11a-86fd37de7293_config-0] Register server push request handler:com.alibaba.nacos.common.remote.client.RpcClient$ConnectResetRequestHandler
2025-07-16 09:03:20 [main] INFO [com.alibaba.nacos.common.remote.client] - [745c3cf4-7cb2-4ac6-a11a-86fd37de7293_config-0] Register server push request handler:com.alibaba.nacos.common.remote.client.RpcClient$$Lambda/0x000000013d43f118
2025-07-16 09:03:20 [main] INFO [com.alibaba.nacos.client.config.impl.Limiter] - limitTime:5.0
2025-07-16 09:03:20 [main] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-user] & group[DEFAULT_GROUP]
2025-07-16 09:03:20 [main] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-user.properties] & group[DEFAULT_GROUP]
2025-07-16 09:03:20 [main] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-user-local.properties] & group[DEFAULT_GROUP]
2025-07-16 09:03:20 [main] INFO [o.s.c.b.c.PropertySourceBootstrapConfiguration] - Located property source: [BootstrapPropertySource {name='bootstrapProperties-emotion-user-local.properties,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-emotion-user.properties,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-emotion-user,DEFAULT_GROUP'}]
2025-07-16 09:03:20 [main] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-16 09:03:20 [main] INFO [com.emotionmuseum.user.UserApplication] - The following 1 profile is active: "local"
2025-07-16 09:03:21 [main] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Multiple Spring Data modules found, entering strict repository configuration mode
2025-07-16 09:03:21 [main] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2025-07-16 09:03:21 [main] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Finished Spring Data repository scanning in 12 ms. Found 0 Redis repository interfaces.
2025-07-16 09:03:21 [main] INFO [o.springframework.cloud.context.scope.GenericScope] - BeanFactory id=2c694ed0-114c-30e6-aed7-dcee6bca36f0
2025-07-16 09:03:22 [main] INFO [o.s.boot.web.embedded.tomcat.TomcatWebServer] - Tomcat initialized with port(s): 19001 (http)
2025-07-16 09:03:22 [main] INFO [org.apache.catalina.core.StandardService] - Starting service [Tomcat]
2025-07-16 09:03:22 [main] INFO [org.apache.catalina.core.StandardEngine] - Starting Servlet engine: [Apache Tomcat/10.1.5]
2025-07-16 09:03:22 [main] INFO [o.a.c.core.ContainerBase.[Tomcat].[localhost].[/]] - Initializing Spring embedded WebApplicationContext
2025-07-16 09:03:22 [main] INFO [o.s.b.w.s.c.ServletWebServerApplicationContext] - Root WebApplicationContext: initialization completed in 2214 ms
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
2025-07-16 09:03:23 [main] INFO [com.emotionmuseum.common.config.SnowflakeConfig] - 使用MAC地址生成的机器ID: 669
2025-07-16 09:03:23 [main] INFO [com.emotionmuseum.common.config.SnowflakeConfig] - 雪花算法配置完成,使用机器ID: 669
2025-07-16 09:03:23 [main] INFO [c.emotionmuseum.common.util.SnowflakeIdGenerator] - 雪花算法ID生成器初始化完成,机器ID: 669
Registered plugin: 'com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor@76889e60'
Parsed mapper file: 'URL [jar:file:/Users/huazhongmin/peanut/AppleDevelop/EmotionMuseum/backend/emotion-user/target/emotion-user-1.0.0.jar!/BOOT-INF/classes!/mapper/UserMapper.xml]'
_ _ |_ _ _|_. ___ _ | _
| | |\/|_)(_| | |_\ |_)||_|_\
/ |
3.5.3.1
2025-07-16 09:03:24 [main] DEBUG [c.e.user.security.JwtAuthenticationFilter] - Filter 'jwtAuthenticationFilter' configured for use
2025-07-16 09:03:24 [main] DEBUG [o.s.web.filter.ServerHttpObservationFilter] - Filter 'serverHttpObservationFilter' configured for use
2025-07-16 09:03:24 [main] DEBUG [o.s.w.s.m.m.a.RequestMappingHandlerMapping] - 19 mappings in 'requestMappingHandlerMapping'
2025-07-16 09:03:24 [main] DEBUG [o.s.web.servlet.handler.SimpleUrlHandlerMapping] - Patterns [/webjars/**, /**] in 'resourceHandlerMapping'
2025-07-16 09:03:25 [main] INFO [o.s.b.actuate.endpoint.web.EndpointLinksResolver] - Exposing 3 endpoint(s) beneath base path '/actuator'
2025-07-16 09:03:25 [main] INFO [o.s.security.web.DefaultSecurityFilterChain] - Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@6993c8df, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@57545c3f, org.springframework.security.web.context.SecurityContextHolderFilter@64920dc2, org.springframework.security.web.header.HeaderWriterFilter@794366a5, org.springframework.web.filter.CorsFilter@326e0b8e, org.springframework.security.web.authentication.logout.LogoutFilter@30839e44, com.emotionmuseum.user.security.JwtAuthenticationFilter@434514d8, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@493ac8d3, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@13dbed9e, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@36baa049, org.springframework.security.web.session.SessionManagementFilter@1ee47d9e, org.springframework.security.web.access.ExceptionTranslationFilter@3f36e8d1, org.springframework.security.web.access.intercept.AuthorizationFilter@7978e022]
2025-07-16 09:03:25 [main] DEBUG [o.s.w.s.m.m.a.RequestMappingHandlerAdapter] - ControllerAdvice beans: 0 @ModelAttribute, 0 @InitBinder, 1 RequestBodyAdvice, 1 ResponseBodyAdvice
2025-07-16 09:03:25 [main] DEBUG [o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver] - ControllerAdvice beans: 0 @ExceptionHandler, 1 ResponseBodyAdvice
2025-07-16 09:03:25 [main] INFO [o.s.boot.web.embedded.tomcat.TomcatWebServer] - Tomcat started on port(s): 19001 (http) with context path ''
2025-07-16 09:03:25 [main] INFO [com.emotionmuseum.user.UserApplication] - Started UserApplication in 7.27 seconds (process running for 7.838)
2025-07-16 09:03:57 [http-nio-19001-exec-1] INFO [o.a.c.core.ContainerBase.[Tomcat].[localhost].[/]] - Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-07-16 09:03:57 [http-nio-19001-exec-1] INFO [org.springframework.web.servlet.DispatcherServlet] - Initializing Servlet 'dispatcherServlet'
2025-07-16 09:03:57 [http-nio-19001-exec-1] DEBUG [org.springframework.web.servlet.DispatcherServlet] - Detected StandardServletMultipartResolver
2025-07-16 09:03:57 [http-nio-19001-exec-1] DEBUG [org.springframework.web.servlet.DispatcherServlet] - Detected AcceptHeaderLocaleResolver
2025-07-16 09:03:57 [http-nio-19001-exec-1] DEBUG [org.springframework.web.servlet.DispatcherServlet] - Detected FixedThemeResolver
2025-07-16 09:03:57 [http-nio-19001-exec-1] DEBUG [org.springframework.web.servlet.DispatcherServlet] - Detected org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator@5e240cb6
2025-07-16 09:03:57 [http-nio-19001-exec-1] DEBUG [org.springframework.web.servlet.DispatcherServlet] - Detected org.springframework.web.servlet.support.SessionFlashMapManager@2e21a5d9
2025-07-16 09:03:57 [http-nio-19001-exec-1] DEBUG [org.springframework.web.servlet.DispatcherServlet] - enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
2025-07-16 09:03:57 [http-nio-19001-exec-1] INFO [org.springframework.web.servlet.DispatcherServlet] - Completed initialization in 2 ms
2025-07-16 09:03:57 [http-nio-19001-exec-1] DEBUG [org.springframework.web.servlet.DispatcherServlet] - GET "/actuator/health", parameters={}
2025-07-16 09:03:57 [http-nio-19001-exec-1] INFO [com.zaxxer.hikari.HikariDataSource] - HikariPool-1 - Starting...
2025-07-16 09:03:57 [http-nio-19001-exec-1] INFO [com.zaxxer.hikari.pool.HikariPool] - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@1703fbc9
2025-07-16 09:03:57 [http-nio-19001-exec-1] INFO [com.zaxxer.hikari.HikariDataSource] - HikariPool-1 - Start completed.
2025-07-16 09:03:58 [http-nio-19001-exec-1] DEBUG [o.s.w.s.m.m.annotation.HttpEntityMethodProcessor] - Using 'application/vnd.spring-boot.actuator.v3+json', given [*/*] and supported [application/vnd.spring-boot.actuator.v3+json, application/vnd.spring-boot.actuator.v2+json, application/json]
2025-07-16 09:03:58 [http-nio-19001-exec-1] DEBUG [o.s.w.s.m.m.annotation.HttpEntityMethodProcessor] - Writing [org.springframework.boot.actuate.health.SystemHealth@178369bf]
2025-07-16 09:03:58 [http-nio-19001-exec-1] DEBUG [org.springframework.web.servlet.DispatcherServlet] - Completed 200 OK
2025-07-16 09:04:12 [http-nio-19001-exec-3] DEBUG [org.springframework.web.servlet.DispatcherServlet] - GET "/actuator/health", parameters={}
2025-07-16 09:04:12 [http-nio-19001-exec-3] DEBUG [o.s.w.s.m.m.annotation.HttpEntityMethodProcessor] - Using 'application/vnd.spring-boot.actuator.v3+json', given [*/*] and supported [application/vnd.spring-boot.actuator.v3+json, application/vnd.spring-boot.actuator.v2+json, application/json]
2025-07-16 09:04:12 [http-nio-19001-exec-3] DEBUG [o.s.w.s.m.m.annotation.HttpEntityMethodProcessor] - Writing [org.springframework.boot.actuate.health.SystemHealth@159e4af4]
2025-07-16 09:04:12 [http-nio-19001-exec-3] DEBUG [org.springframework.web.servlet.DispatcherServlet] - Completed 200 OK
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.web.servlet.handler.SimpleUrlHandlerMapping] - Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.web.servlet.handler.SimpleUrlHandlerMapping] - Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.web.servlet.handler.SimpleUrlHandlerMapping] - Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.web.servlet.handler.SimpleUrlHandlerMapping] - Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.web.servlet.handler.SimpleUrlHandlerMapping] - Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.web.servlet.handler.SimpleUrlHandlerMapping] - Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.web.servlet.handler.SimpleUrlHandlerMapping] - Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.web.servlet.handler.SimpleUrlHandlerMapping] - Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.web.servlet.handler.SimpleUrlHandlerMapping] - Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.web.servlet.handler.SimpleUrlHandlerMapping] - Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.web.servlet.handler.SimpleUrlHandlerMapping] - Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.web.servlet.handler.SimpleUrlHandlerMapping] - Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.web.servlet.handler.SimpleUrlHandlerMapping] - Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.w.s.m.m.a.RequestMappingHandlerMapping] - Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.w.s.m.m.a.RequestMappingHandlerMapping] - Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.w.s.m.m.a.RequestMappingHandlerMapping] - Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.w.s.m.m.a.RequestMappingHandlerMapping] - Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.w.s.m.m.a.RequestMappingHandlerMapping] - Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.w.s.m.m.a.RequestMappingHandlerMapping] - Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.w.s.m.m.a.RequestMappingHandlerMapping] - Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.w.s.m.m.a.RequestMappingHandlerMapping] - Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.w.s.m.m.a.RequestMappingHandlerMapping] - Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.w.s.m.m.a.RequestMappingHandlerMapping] - Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.w.s.m.m.a.RequestMappingHandlerMapping] - Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.w.s.m.m.a.RequestMappingHandlerMapping] - Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.w.s.m.m.a.RequestMappingHandlerMapping] - Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [org.springframework.web.servlet.DispatcherServlet] - "ERROR" dispatch for GET "/error", parameters={}
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.w.s.m.m.a.RequestMappingHandlerMapping] - Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.w.s.m.m.annotation.HttpEntityMethodProcessor] - Using 'application/json', given [*/*] and supported [application/json, application/*+json]
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [o.s.w.s.m.m.annotation.HttpEntityMethodProcessor] - Writing [{timestamp=Wed Jul 16 09:04:22 CST 2025, status=403, error=Forbidden, path=/user/actuator/health}]
2025-07-16 09:04:22 [http-nio-19001-exec-5] DEBUG [org.springframework.web.servlet.DispatcherServlet] - Exiting from "ERROR" dispatch, status 403
2025-07-16 09:46:48 [Thread-1] WARN [c.alibaba.nacos.common.http.HttpClientBeanHolder] - [HttpClientBeanHolder] Start destroying common HttpClient
2025-07-16 09:46:48 [Thread-7] WARN [com.alibaba.nacos.common.notify.NotifyCenter] - [NotifyCenter] Start destroying Publisher
2025-07-16 09:46:48 [Thread-7] WARN [com.alibaba.nacos.common.notify.NotifyCenter] - [NotifyCenter] Destruction of the end
2025-07-16 09:46:48 [Thread-1] WARN [c.alibaba.nacos.common.http.HttpClientBeanHolder] - [HttpClientBeanHolder] Destruction of the end
2025-07-16 09:46:49 [SpringApplicationShutdownHook] INFO [com.zaxxer.hikari.HikariDataSource] - HikariPool-1 - Shutdown initiated...
2025-07-16 09:46:49 [SpringApplicationShutdownHook] INFO [com.zaxxer.hikari.HikariDataSource] - HikariPool-1 - Shutdown completed.
-99
View File
@@ -1,99 +0,0 @@
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.emotionmuseum:emotion-user >-------------------
[INFO] Building emotion-user 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] >>> spring-boot:3.0.2:run (default-cli) > test-compile @ emotion-user >>>
[WARNING] The artifact mysql:mysql-connector-java:jar:8.0.33 has been relocated to com.mysql:mysql-connector-j:jar:8.0.33: MySQL Connector/J artifacts moved to reverse-DNS compliant Maven 2+ coordinates.
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ emotion-user ---
[INFO] Copying 2 resources from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.10.1:compile (default-compile) @ emotion-user ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ emotion-user ---
[INFO] skip non existing resourceDirectory /Users/huazhongmin/peanut/AppleDevelop/EmotionMuseum/backend/emotion-user/src/test/resources
[INFO]
[INFO] --- compiler:3.10.1:testCompile (default-testCompile) @ emotion-user ---
[INFO] No sources to compile
[INFO]
[INFO] <<< spring-boot:3.0.2:run (default-cli) < test-compile @ emotion-user <<<
[INFO]
[INFO]
[INFO] --- spring-boot:3.0.2:run (default-cli) @ emotion-user ---
[INFO] Attaching agents: []
2025-07-13T08:45:36.142+08:00  WARN 6791 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:36.195+08:00  WARN 6791 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:36.221+08:00  INFO 6791 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2025-07-13T08:45:36.387+08:00  WARN 6791 --- [ restartedMain] c.a.nacos.client.logging.NacosLogging  : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13T08:45:36.388+08:00  INFO 6791 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : [settings] [req-serv] nacos-server port:8848
2025-07-13T08:45:36.389+08:00  INFO 6791 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : [settings] [http-client] connect timeout:1000
2025-07-13T08:45:36.391+08:00  INFO 6791 --- [ restartedMain] c.alibaba.nacos.client.utils.ParamUtil  : PER_TASK_CONFIG_SIZE: 3000.0
2025-07-13T08:45:36.437+08:00  INFO 6791 --- [ restartedMain] c.a.n.p.a.s.c.ClientAuthPluginManager  : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.impl.NacosClientAuthServiceImpl success.
2025-07-13T08:45:36.437+08:00  INFO 6791 --- [ restartedMain] c.a.n.p.a.s.c.ClientAuthPluginManager  : [ClientAuthPluginManager] Load ClientAuthService com.alibaba.nacos.client.auth.ram.RamClientAuthServiceImpl success.
2025-07-13T08:45:36.455+08:00  INFO 6791 --- [ restartedMain] c.a.n.c.a.r.identify.CredentialWatcher  : null No credential found
2025-07-13 08:45:36 [restartedMain] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
 :: Spring Boot ::   (v3.0.2)
2025-07-13 08:45:36 [restartedMain] INFO [c.a.n.client.config.impl.LocalConfigInfoProcessor] - LOCAL_SNAPSHOT_PATH:/Users/huazhongmin/nacos/config
2025-07-13 08:45:36 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [RpcClientFactory] create a new rpc client of d7ae7ba9-4fec-4362-a0ff-0d9a56ce7c0d_config-0
2025-07-13 08:45:36 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [d7ae7ba9-4fec-4362-a0ff-0d9a56ce7c0d_config-0] Register server push request handler:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$$Lambda/0x000000012e318670
2025-07-13 08:45:36 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [d7ae7ba9-4fec-4362-a0ff-0d9a56ce7c0d_config-0] Register server push request handler:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$$Lambda/0x000000012e318aa0
2025-07-13 08:45:36 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [d7ae7ba9-4fec-4362-a0ff-0d9a56ce7c0d_config-0] Registry connection listener to current client:com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$1
2025-07-13 08:45:36 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [d7ae7ba9-4fec-4362-a0ff-0d9a56ce7c0d_config-0] RpcClient init, ServerListFactory = com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient$2
2025-07-13 08:45:36 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [d7ae7ba9-4fec-4362-a0ff-0d9a56ce7c0d_config-0] Try to connect to server on start up, server: {serverIp = '127.0.0.1', server main port = 8848}
2025-07-13 08:45:36 [restartedMain] INFO [c.a.nacos.common.remote.client.grpc.GrpcClient] - grpc client connection server:127.0.0.1 ip,serverPort:9848,grpcTslConfig:{"sslProvider":"OPENSSL","enableTls":false,"mutualAuthEnable":false,"trustAll":false}
2025-07-13 08:45:37 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [d7ae7ba9-4fec-4362-a0ff-0d9a56ce7c0d_config-0] Success to connect to server [127.0.0.1:8848] on start up, connectionId = 1752367537016_127.0.0.1_51165
2025-07-13 08:45:37 [com.alibaba.nacos.client.remote.worker] INFO [com.alibaba.nacos.common.remote.client] - [d7ae7ba9-4fec-4362-a0ff-0d9a56ce7c0d_config-0] Notify connected event to listeners.
2025-07-13 08:45:37 [com.alibaba.nacos.client.remote.worker] INFO [com.alibaba.nacos.client.config.impl.ClientWorker] - [d7ae7ba9-4fec-4362-a0ff-0d9a56ce7c0d_config-0] Connected,notify listen context...
2025-07-13 08:45:37 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [d7ae7ba9-4fec-4362-a0ff-0d9a56ce7c0d_config-0] Register server push request handler:com.alibaba.nacos.common.remote.client.RpcClient$ConnectResetRequestHandler
2025-07-13 08:45:37 [restartedMain] INFO [com.alibaba.nacos.common.remote.client] - [d7ae7ba9-4fec-4362-a0ff-0d9a56ce7c0d_config-0] Register server push request handler:com.alibaba.nacos.common.remote.client.RpcClient$$Lambda/0x000000012e4a2a18
2025-07-13 08:45:37 [restartedMain] INFO [com.alibaba.nacos.client.config.impl.Limiter] - limitTime:5.0
2025-07-13 08:45:37 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-user] & group[DEFAULT_GROUP]
2025-07-13 08:45:37 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-user.properties] & group[DEFAULT_GROUP]
2025-07-13 08:45:37 [restartedMain] WARN [c.a.cloud.nacos.client.NacosPropertySourceBuilder] - Ignore the empty nacos configuration and get it based on dataId[emotion-user-dev.properties] & group[DEFAULT_GROUP]
2025-07-13 08:45:37 [restartedMain] INFO [o.s.c.b.c.PropertySourceBootstrapConfiguration] - Located property source: [BootstrapPropertySource {name='bootstrapProperties-emotion-user-dev.properties,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-emotion-user.properties,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-emotion-user,DEFAULT_GROUP'}]
2025-07-13 08:45:37 [restartedMain] WARN [com.alibaba.nacos.client.logging.NacosLogging] - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-13 08:45:37 [restartedMain] INFO [com.emotionmuseum.user.UserApplication] - The following 1 profile is active: "dev"
2025-07-13 08:45:37 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Multiple Spring Data modules found, entering strict repository configuration mode
2025-07-13 08:45:37 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2025-07-13 08:45:37 [restartedMain] INFO [o.s.d.r.config.RepositoryConfigurationDelegate] - Finished Spring Data repository scanning in 7 ms. Found 0 Redis repository interfaces.
2025-07-13 08:45:38 [restartedMain] INFO [o.springframework.cloud.context.scope.GenericScope] - BeanFactory id=7e3acccb-1d48-37df-bf0c-1c15c4e1d510
2025-07-13 08:45:38 [restartedMain] INFO [o.s.boot.web.embedded.tomcat.TomcatWebServer] - Tomcat initialized with port(s): 9001 (http)
2025-07-13 08:45:38 [restartedMain] INFO [org.apache.catalina.core.StandardService] - Starting service [Tomcat]
2025-07-13 08:45:38 [restartedMain] INFO [org.apache.catalina.core.StandardEngine] - Starting Servlet engine: [Apache Tomcat/10.1.5]
2025-07-13 08:45:38 [restartedMain] INFO [o.a.c.core.ContainerBase.[Tomcat].[localhost].[/]] - Initializing Spring embedded WebApplicationContext
2025-07-13 08:45:38 [restartedMain] INFO [o.s.b.w.s.c.ServletWebServerApplicationContext] - Root WebApplicationContext: initialization completed in 1416 ms
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
Registered plugin: 'com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor@7d6aa779'
Parsed mapper file: 'file [/Users/huazhongmin/peanut/AppleDevelop/EmotionMuseum/backend/emotion-user/target/classes/mapper/UserMapper.xml]'
_ _ |_ _ _|_. ___ _ | _
| | |\/|_)(_| | |_\ |_)||_|_\
/ |
3.5.3.1
2025-07-13 08:45:39 [restartedMain] WARN [o.s.b.d.autoconfigure.OptionalLiveReloadServer] - Unable to start LiveReload server
2025-07-13 08:45:39 [restartedMain] INFO [o.s.b.actuate.endpoint.web.EndpointLinksResolver] - Exposing 3 endpoint(s) beneath base path '/actuator'
2025-07-13 08:45:39 [restartedMain] INFO [o.s.boot.web.embedded.tomcat.TomcatWebServer] - Tomcat started on port(s): 9001 (http) with context path ''
2025-07-13 08:45:39 [restartedMain] INFO [com.emotionmuseum.user.UserApplication] - Started UserApplication in 3.951 seconds (process running for 4.401)
2025-07-13 08:45:40 [http-nio-9001-exec-1] INFO [o.a.c.core.ContainerBase.[Tomcat].[localhost].[/]] - Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-07-13 08:45:40 [http-nio-9001-exec-1] INFO [org.springframework.web.servlet.DispatcherServlet] - Initializing Servlet 'dispatcherServlet'
2025-07-13 08:45:40 [http-nio-9001-exec-1] INFO [org.springframework.web.servlet.DispatcherServlet] - Completed initialization in 1 ms
2025-07-13 08:45:40 [http-nio-9001-exec-1] INFO [com.zaxxer.hikari.HikariDataSource] - HikariPool-1 - Starting...
2025-07-13 08:45:40 [http-nio-9001-exec-1] INFO [com.zaxxer.hikari.pool.HikariPool] - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@3eaf6ca4
2025-07-13 08:45:40 [http-nio-9001-exec-1] INFO [com.zaxxer.hikari.HikariDataSource] - HikariPool-1 - Start completed.
2025-07-13 08:49:25 [Thread-4] WARN [c.alibaba.nacos.common.http.HttpClientBeanHolder] - [HttpClientBeanHolder] Start destroying common HttpClient
2025-07-13 08:49:25 [Thread-10] WARN [com.alibaba.nacos.common.notify.NotifyCenter] - [NotifyCenter] Start destroying Publisher
2025-07-13 08:49:25 [Thread-10] WARN [com.alibaba.nacos.common.notify.NotifyCenter] - [NotifyCenter] Destruction of the end
2025-07-13 08:49:25 [Thread-4] WARN [c.alibaba.nacos.common.http.HttpClientBeanHolder] - [HttpClientBeanHolder] Destruction of the end
2025-07-13 08:49:25 [SpringApplicationShutdownHook] INFO [com.zaxxer.hikari.HikariDataSource] - HikariPool-1 - Shutdown initiated...
2025-07-13 08:49:25 [SpringApplicationShutdownHook] INFO [com.zaxxer.hikari.HikariDataSource] - HikariPool-1 - Shutdown completed.
-1
View File
@@ -1 +0,0 @@
target/emotion-gateway-1.0.0.jar中没有主清单属性
-1
View File
@@ -1 +0,0 @@
63251
-32
View File
@@ -1,32 +0,0 @@
2025-07-15T17:59:48.763+08:00 WARN 62881 --- [ main] c.a.nacos.client.logging.NacosLogging : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-15T17:59:48.862+08:00 WARN 62881 --- [ main] c.a.nacos.client.logging.NacosLogging : Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-15 17:59:49 [main] WARN com.alibaba.nacos.client.logging.NacosLogging - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.0.2)
2025-07-15 17:59:49 [main] WARN com.alibaba.nacos.client.logging.NacosLogging - Load Logback Configuration of Nacos fail, message: Could not initialize Logback Nacos logging from classpath:nacos-logback.xml
2025-07-15 17:59:49 [main] INFO com.emotionmuseum.user.UserApplication - The following 1 profile is active: "local"
2025-07-15 17:59:50 [main] WARN o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'snowflakeIdGenerator' defined in class path resource [com/emotionmuseum/common/config/SnowflakeConfig.class]: Cannot register bean definition [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=snowflakeConfig; factoryMethodName=snowflakeIdGenerator; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in class path resource [com/emotionmuseum/common/config/SnowflakeConfig.class]] for bean 'snowflakeIdGenerator' since there is already [Generic bean: class [com.emotionmuseum.common.util.SnowflakeIdGenerator]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null; defined in URL [jar:file:/Users/huazhongmin/peanut/AppleDevelop/EmotionMuseum/backend/emotion-user/target/emotion-user-1.0.0.jar!/BOOT-INF/lib/emotion-common-1.0.0.jar!/com/emotionmuseum/common/util/SnowflakeIdGenerator.class]] bound.
2025-07-15 17:59:50 [main] INFO o.s.b.a.logging.ConditionEvaluationReportLogger -
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2025-07-15 17:59:50 [main] ERROR o.s.b.diagnostics.LoggingFailureAnalysisReporter -
***************************
APPLICATION FAILED TO START
***************************
Description:
The bean 'snowflakeIdGenerator', defined in class path resource [com/emotionmuseum/common/config/SnowflakeConfig.class], could not be registered. A bean with that name has already been defined in URL [jar:file:/Users/huazhongmin/peanut/AppleDevelop/EmotionMuseum/backend/emotion-user/target/emotion-user-1.0.0.jar!/BOOT-INF/lib/emotion-common-1.0.0.jar!/com/emotionmuseum/common/util/SnowflakeIdGenerator.class] and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
-1
View File
@@ -1 +0,0 @@
62881
-134
View File
@@ -1,134 +0,0 @@
#!/bin/bash
# 情感博物馆微服务启动脚本
# 支持环境参数控制
# 默认环境
DEFAULT_ENV="local"
ENV=${1:-$DEFAULT_ENV}
echo "=========================================="
echo "情感博物馆微服务启动脚本"
echo "启动环境: $ENV"
echo "=========================================="
# 检查基础服务
check_services() {
echo "📊 检查基础服务..."
if ! nc -z localhost 3306; then
echo "❌ MySQL服务未启动,请先启动MySQL服务"
echo "可以使用: brew services start mysql"
exit 1
fi
if ! nc -z localhost 6379; then
echo "❌ Redis服务未启动,请先启动Redis服务"
echo "可以使用: brew services start redis"
exit 1
fi
echo "✅ 基础服务检查通过"
}
# 启动服务函数
start_service() {
local service_name=$1
local port=$2
local description=$3
echo "🚀 启动 $description ($service_name:$port)..."
cd $service_name
# 检查端口是否被占用
if lsof -Pi :$port -sTCP:LISTEN -t >/dev/null ; then
echo "⚠️ 端口 $port 已被占用,跳过 $service_name"
cd ..
return
fi
# 编译项目
echo "📦 编译 $service_name..."
mvn clean package -DskipTests -q
if [ $? -ne 0 ]; then
echo "$service_name 编译失败"
cd ..
return
fi
# 启动服务
echo "🔄 启动 $service_name (环境: $ENV)..."
nohup java -jar -Dspring.profiles.active=$ENV target/$service_name-1.0.0.jar > ../logs/$service_name-$ENV.log 2>&1 &
# 记录PID
echo $! > ../logs/$service_name.pid
echo "$service_name 启动完成,PID: $!"
echo "📋 日志文件: logs/$service_name-$ENV.log"
cd ..
# 等待服务启动
sleep 3
}
# 创建日志目录
mkdir -p logs
# 检查基础服务
check_services
# 服务列表
services=(
"emotion-user:19001:用户服务"
"emotion-ai:19002:AI服务"
"emotion-websocket:19007:WebSocket聊天服务"
"emotion-gateway:19000:网关服务"
)
echo "🚀 开始启动核心服务 (环境: $ENV)..."
# 按顺序启动服务
for service_info in "${services[@]}"; do
IFS=':' read -r service_name port description <<< "$service_info"
start_service "$service_name" "$port" "$description"
done
echo ""
echo "🎉 核心服务启动完成!"
echo ""
echo "📋 服务列表:"
for service_info in "${services[@]}"; do
IFS=':' read -r service_name port description <<< "$service_info"
echo " $description: http://localhost:$port"
done
echo ""
echo "📝 使用 './stop-services.sh' 停止所有服务"
echo "📝 查看日志: tail -f logs/服务名-$ENV.log"
# 等待服务完全启动
echo ""
echo "📊 等待服务完全启动..."
sleep 15
# 检查服务状态
echo "📊 检查服务状态..."
for service_info in "${services[@]}"; do
IFS=':' read -r service_name port description <<< "$service_info"
if curl -s http://localhost:$port/actuator/health >/dev/null 2>&1; then
echo "$description 运行正常"
else
echo "⚠️ $description 可能未完全启动,请查看日志: tail -f logs/$service_name-$ENV.log"
fi
done
echo ""
echo "🎉 启动完成!环境: $ENV"
echo ""
echo "使用方法:"
echo " ./start-services.sh # 使用默认local环境启动"
echo " ./start-services.sh dev # 使用dev环境启动"
echo " ./start-services.sh prod # 使用prod环境启动"
-92
View File
@@ -1,92 +0,0 @@
#!/bin/bash
# 情绪博物馆微服务停止脚本
# 作者: emotion-museum
# 日期: 2025-07-12
echo "=========================================="
echo "情绪博物馆微服务停止脚本"
echo "=========================================="
# 停止服务函数
stop_service() {
local service_name=$1
local pid_file="logs/${service_name}.pid"
if [ -f "$pid_file" ]; then
local pid=$(cat $pid_file)
if ps -p $pid > /dev/null 2>&1; then
echo "停止 $service_name 服务 (PID: $pid)..."
kill $pid
# 等待进程结束
local count=0
while [ $count -lt 10 ]; do
if ! ps -p $pid > /dev/null 2>&1; then
echo "$service_name 服务已停止"
rm -f $pid_file
return 0
fi
sleep 1
count=$((count + 1))
done
# 强制杀死进程
echo "强制停止 $service_name 服务..."
kill -9 $pid 2>/dev/null
rm -f $pid_file
else
echo "$service_name 服务未运行"
rm -f $pid_file
fi
else
echo "$service_name 服务PID文件不存在"
fi
}
# 停止所有微服务
echo "开始停止微服务..."
# 停止统计分析服务
stop_service "emotion-stats"
# 停止成就奖励服务
stop_service "emotion-reward"
# 停止地图探索服务
stop_service "emotion-explore"
# 停止成长课题服务
stop_service "emotion-growth"
# 停止情绪记录服务
stop_service "emotion-record"
# 停止AI对话服务
stop_service "emotion-ai"
# 停止WebSocket聊天服务
stop_service "emotion-websocket"
# 停止用户服务
stop_service "emotion-user"
# 停止网关服务
stop_service "emotion-gateway"
# 清理可能残留的Java进程
echo "清理残留进程..."
pkill -f "emotion-gateway"
pkill -f "emotion-user"
pkill -f "emotion-ai"
pkill -f "emotion-websocket"
pkill -f "emotion-record"
pkill -f "emotion-growth"
pkill -f "emotion-explore"
pkill -f "emotion-reward"
pkill -f "emotion-stats"
echo ""
echo "=========================================="
echo "所有微服务已停止!"
echo "=========================================="
-90
View File
@@ -1,90 +0,0 @@
#!/bin/bash
# 认证功能测试脚本
# 用于测试emotion-auth模块的认证功能
BASE_URL="http://localhost:19000/api/auth"
CONTENT_TYPE="Content-Type: application/json"
echo "========================================="
echo "开始测试emotion-auth模块认证功能"
echo "========================================="
# 1. 测试获取验证码
echo "1. 测试获取验证码..."
CAPTCHA_RESPONSE=$(curl -s -X GET "${BASE_URL}/../captcha/generate")
echo "验证码响应: $CAPTCHA_RESPONSE"
# 提取验证码ID(假设返回JSON格式)
CAPTCHA_ID=$(echo $CAPTCHA_RESPONSE | grep -o '"key":"[^"]*"' | cut -d'"' -f4)
echo "验证码ID: $CAPTCHA_ID"
# 2. 测试用户注册
echo -e "\n2. 测试用户注册..."
REGISTER_DATA='{
"account": "testuser001",
"password": "123456",
"confirmPassword": "123456",
"email": "test@example.com",
"captcha": "1234",
"captchaId": "'$CAPTCHA_ID'"
}'
REGISTER_RESPONSE=$(curl -s -X POST "${BASE_URL}/register" \
-H "$CONTENT_TYPE" \
-d "$REGISTER_DATA")
echo "注册响应: $REGISTER_RESPONSE"
# 3. 测试用户登录
echo -e "\n3. 测试用户登录..."
LOGIN_DATA='{
"account": "testuser001",
"password": "123456",
"captcha": "1234",
"captchaId": "'$CAPTCHA_ID'"
}'
LOGIN_RESPONSE=$(curl -s -X POST "${BASE_URL}/login" \
-H "$CONTENT_TYPE" \
-d "$LOGIN_DATA")
echo "登录响应: $LOGIN_RESPONSE"
# 提取访问Token
ACCESS_TOKEN=$(echo $LOGIN_RESPONSE | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4)
echo "访问Token: $ACCESS_TOKEN"
# 4. 测试获取用户信息
if [ ! -z "$ACCESS_TOKEN" ]; then
echo -e "\n4. 测试获取用户信息..."
USER_INFO_RESPONSE=$(curl -s -X GET "${BASE_URL}/user-info" \
-H "$CONTENT_TYPE" \
-H "Authorization: Bearer $ACCESS_TOKEN")
echo "用户信息响应: $USER_INFO_RESPONSE"
fi
# 5. 测试验证Token
if [ ! -z "$ACCESS_TOKEN" ]; then
echo -e "\n5. 测试验证Token..."
VALIDATE_RESPONSE=$(curl -s -X GET "${BASE_URL}/validate-token" \
-H "$CONTENT_TYPE" \
-H "Authorization: Bearer $ACCESS_TOKEN")
echo "Token验证响应: $VALIDATE_RESPONSE"
fi
# 6. 测试检查账号是否存在
echo -e "\n6. 测试检查账号是否存在..."
CHECK_ACCOUNT_RESPONSE=$(curl -s -X GET "${BASE_URL}/check-account?account=testuser001")
echo "检查账号响应: $CHECK_ACCOUNT_RESPONSE"
# 7. 测试用户登出
if [ ! -z "$ACCESS_TOKEN" ]; then
echo -e "\n7. 测试用户登出..."
LOGOUT_RESPONSE=$(curl -s -X POST "${BASE_URL}/logout?userId=test-user-id" \
-H "$CONTENT_TYPE" \
-H "Authorization: Bearer $ACCESS_TOKEN")
echo "登出响应: $LOGOUT_RESPONSE"
fi
echo -e "\n========================================="
echo "认证功能测试完成"
echo "========================================="
-255
View File
@@ -1,255 +0,0 @@
#!/bin/bash
# ============================================================================
# 批量更新所有微服务的Nacos配置
# 为每个服务创建本地、测试、生产环境的配置文件
# ============================================================================
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}===========================================${NC}"
echo -e "${BLUE}批量更新微服务Nacos配置${NC}"
echo -e "${BLUE}===========================================${NC}"
# 服务配置
SERVICES="emotion-user:19001 emotion-ai:19002 emotion-record:19003 emotion-growth:19004 emotion-explore:19005 emotion-reward:19006 emotion-websocket:19007 emotion-stats:19008"
# 生成本地环境配置
generate_local_config() {
local service_name=$1
local port=$2
cat > "backend/${service_name}/src/main/resources/application-local.yml" << EOF
# 本地开发环境配置
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
# 日志配置
logging:
level:
com.emotionmuseum: debug
com.baomidou.mybatisplus: debug
com.alibaba.nacos: info
file:
name: logs/${service_name}-local.log
EOF
}
# 生成测试环境配置
generate_test_config() {
local service_name=$1
local port=$2
cat > "backend/${service_name}/src/main/resources/application-test.yml" << EOF
# 测试环境配置
spring:
cloud:
nacos:
discovery:
server-addr: 47.111.10.27:8848
namespace: test
group: DEFAULT_GROUP
enabled: true
username: nacos
password: nacos
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: 47.111.10.27:8848
namespace: test
group: DEFAULT_GROUP
file-extension: yml
enabled: false
username: nacos
password: nacos
# 数据源配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.111.10.27:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
username: root
password: EmotionMuseum2025*#
# Redis配置
data:
redis:
host: 47.111.10.27
port: 6379
password: EmotionMuseum2025*#
database: 0
# 日志配置
logging:
level:
com.emotionmuseum: info
com.baomidou.mybatisplus: info
com.alibaba.nacos: warn
file:
name: logs/${service_name}-test.log
EOF
}
# 生成生产环境配置
generate_prod_config() {
local service_name=$1
local port=$2
cat > "backend/${service_name}/src/main/resources/application-prod.yml" << EOF
# 生产环境配置
spring:
cloud:
nacos:
discovery:
server-addr: 47.111.10.27:8848
namespace: prod
group: DEFAULT_GROUP
enabled: true
username: nacos
password: nacos
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: 47.111.10.27:8848
namespace: prod
group: DEFAULT_GROUP
file-extension: yml
enabled: false
username: nacos
password: nacos
# 数据源配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.111.10.27:3306/emotion_museum?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
username: root
password: EmotionMuseum2025*#
# Redis配置
data:
redis:
host: 47.111.10.27
port: 6379
password: EmotionMuseum2025*#
database: 0
# 日志配置
logging:
level:
com.emotionmuseum: warn
com.baomidou.mybatisplus: warn
com.alibaba.nacos: error
file:
name: logs/${service_name}-prod.log
EOF
}
# 处理每个服务
for service_info in $SERVICES; do
service_name=$(echo $service_info | cut -d: -f1)
port=$(echo $service_info | cut -d: -f2)
echo -e "${YELLOW}处理服务: $service_name (端口: $port)${NC}"
# 检查服务目录是否存在
if [ ! -d "backend/$service_name" ]; then
echo -e "${RED}❌ 服务目录不存在: backend/$service_name${NC}"
continue
fi
# 创建resources目录(如果不存在)
mkdir -p "backend/$service_name/src/main/resources"
# 生成配置文件
echo " 生成本地环境配置..."
generate_local_config "$service_name" "$port"
echo " 生成测试环境配置..."
generate_test_config "$service_name" "$port"
echo " 生成生产环境配置..."
generate_prod_config "$service_name" "$port"
echo -e "${GREEN}$service_name 配置文件生成完成${NC}"
done
echo ""
echo -e "${BLUE}===========================================${NC}"
echo -e "${GREEN}✅ 所有服务的Nacos配置更新完成!${NC}"
echo -e "${BLUE}===========================================${NC}"
echo ""
echo -e "${YELLOW}配置说明:${NC}"
echo -e "1. 本地环境: localhost:8848, 无命名空间"
echo -e "2. 测试环境: 47.111.10.27:8848, test命名空间"
echo -e "3. 生产环境: 47.111.10.27:8848, prod命名空间"
echo ""
echo -e "${YELLOW}使用方法:${NC}"
echo -e "启动时指定环境: ${GREEN}--spring.profiles.active=local|test|prod${NC}"
-157
View File
@@ -1,157 +0,0 @@
#!/bin/bash
# ============================================================================
# 批量更新所有微服务的Nacos密码配置
# 本地环境: Peanut2817*#
# 测试和生产环境: EmotionMuseum2025
# ============================================================================
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}===========================================${NC}"
echo -e "${BLUE}批量更新微服务Nacos密码配置${NC}"
echo -e "${BLUE}===========================================${NC}"
# 服务列表
SERVICES="emotion-user emotion-ai emotion-record emotion-growth emotion-explore emotion-reward emotion-websocket emotion-stats"
# 更新本地环境密码
update_local_password() {
local service_name=$1
local config_file="backend/${service_name}/src/main/resources/application-local.yml"
if [ ! -f "$config_file" ]; then
echo -e "${RED} ❌ 配置文件不存在: $config_file${NC}"
return 1
fi
# 使用sed替换密码
sed -i.bak 's/password: nacos$/password: Peanut2817*#/g' "$config_file"
# 检查是否替换成功
if grep -q "password: Peanut2817*#" "$config_file"; then
echo -e "${GREEN} ✅ 本地环境密码更新成功${NC}"
rm -f "${config_file}.bak"
return 0
else
echo -e "${RED} ❌ 本地环境密码更新失败${NC}"
return 1
fi
}
# 更新测试环境密码
update_test_password() {
local service_name=$1
local config_file="backend/${service_name}/src/main/resources/application-test.yml"
if [ ! -f "$config_file" ]; then
echo -e "${RED} ❌ 配置文件不存在: $config_file${NC}"
return 1
fi
# 使用sed替换密码
sed -i.bak 's/password: nacos$/password: EmotionMuseum2025/g' "$config_file"
# 检查是否替换成功
if grep -q "password: EmotionMuseum2025" "$config_file"; then
echo -e "${GREEN} ✅ 测试环境密码更新成功${NC}"
rm -f "${config_file}.bak"
return 0
else
echo -e "${RED} ❌ 测试环境密码更新失败${NC}"
return 1
fi
}
# 更新生产环境密码
update_prod_password() {
local service_name=$1
local config_file="backend/${service_name}/src/main/resources/application-prod.yml"
if [ ! -f "$config_file" ]; then
echo -e "${RED} ❌ 配置文件不存在: $config_file${NC}"
return 1
fi
# 使用sed替换密码
sed -i.bak 's/password: nacos$/password: EmotionMuseum2025/g' "$config_file"
# 检查是否替换成功
if grep -q "password: EmotionMuseum2025" "$config_file"; then
echo -e "${GREEN} ✅ 生产环境密码更新成功${NC}"
rm -f "${config_file}.bak"
return 0
else
echo -e "${RED} ❌ 生产环境密码更新失败${NC}"
return 1
fi
}
# 统计结果
total_services=0
success_services=0
failed_services=0
# 处理每个服务
for service_name in $SERVICES; do
echo -e "${YELLOW}更新服务: $service_name${NC}"
total_services=$((total_services + 1))
# 检查服务目录是否存在
if [ ! -d "backend/$service_name" ]; then
echo -e "${RED} ❌ 服务目录不存在: backend/$service_name${NC}"
failed_services=$((failed_services + 1))
continue
fi
# 更新各环境密码
service_success=true
if ! update_local_password "$service_name"; then
service_success=false
fi
if ! update_test_password "$service_name"; then
service_success=false
fi
if ! update_prod_password "$service_name"; then
service_success=false
fi
if [ "$service_success" = true ]; then
echo -e "${GREEN}$service_name 密码更新完成${NC}"
success_services=$((success_services + 1))
else
echo -e "${RED}$service_name 密码更新失败${NC}"
failed_services=$((failed_services + 1))
fi
echo ""
done
# 显示统计结果
echo -e "${BLUE}===========================================${NC}"
echo -e "${BLUE}密码更新结果统计${NC}"
echo -e "${BLUE}===========================================${NC}"
echo -e "总服务数: ${BLUE}$total_services${NC}"
echo -e "更新成功: ${GREEN}$success_services${NC}"
echo -e "更新失败: ${RED}$failed_services${NC}"
if [ $failed_services -eq 0 ]; then
echo -e "${GREEN}🎉 所有服务密码更新完成!${NC}"
echo ""
echo -e "${YELLOW}密码配置:${NC}"
echo -e "本地环境: ${GREEN}Peanut2817*#${NC}"
echo -e "测试环境: ${GREEN}EmotionMuseum2025${NC}"
echo -e "生产环境: ${GREEN}EmotionMuseum2025${NC}"
exit 0
else
echo -e "${RED}❌ 有 $failed_services 个服务密码更新失败${NC}"
exit 1
fi
-81
View File
@@ -1,81 +0,0 @@
-- ============================================================================
-- 数据库脚本验证查询
-- 用于验证 mysql_emotion_museum_final.sql 执行后的表结构
-- ============================================================================
-- 验证数据库是否存在
SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = 'emotion_museum';
-- 验证所有表是否创建成功
SELECT TABLE_NAME, TABLE_COMMENT
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'emotion_museum'
ORDER BY TABLE_NAME;
-- 验证conversation表的字段结构(重点验证新增字段)
SELECT
COLUMN_NAME,
DATA_TYPE,
IS_NULLABLE,
COLUMN_DEFAULT,
COLUMN_COMMENT
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'emotion_museum'
AND TABLE_NAME = 'conversation'
ORDER BY ORDINAL_POSITION;
-- 验证conversation表的索引
SELECT
INDEX_NAME,
COLUMN_NAME,
NON_UNIQUE
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = 'emotion_museum'
AND TABLE_NAME = 'conversation'
ORDER BY INDEX_NAME, SEQ_IN_INDEX;
-- 验证新增字段是否存在
SELECT
CASE
WHEN COUNT(*) = 9 THEN '✅ 所有新增字段都存在'
ELSE CONCAT('❌ 缺少字段,只找到 ', COUNT(*), ' 个,应该是 9 个')
END AS validation_result
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'emotion_museum'
AND TABLE_NAME = 'conversation'
AND COLUMN_NAME IN (
'user_type', 'emotion_trend', 'keywords', 'ai_insights',
'confidence', 'client_ip', 'user_agent', 'summary', 'tags'
);
-- 验证新增索引是否存在
SELECT
CASE
WHEN COUNT(*) = 4 THEN '✅ 所有新增索引都存在'
ELSE CONCAT('❌ 缺少索引,只找到 ', COUNT(*), ' 个,应该是 4 个')
END AS index_validation_result
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = 'emotion_museum'
AND TABLE_NAME = 'conversation'
AND INDEX_NAME IN (
'idx_conversation_user_type',
'idx_conversation_emotion_trend',
'idx_conversation_confidence',
'idx_conversation_client_ip'
);
-- 统计总表数
SELECT
CASE
WHEN COUNT(*) = 15 THEN '✅ 所有15个表都创建成功'
ELSE CONCAT('❌ 表数量不正确,只有 ', COUNT(*), ' 个表,应该是 15 个')
END AS table_count_result
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'emotion_museum';
-- 统计总索引数(conversation表)
SELECT
CONCAT('conversation表共有 ', COUNT(DISTINCT INDEX_NAME), ' 个索引') AS conversation_index_count
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = 'emotion_museum'
AND TABLE_NAME = 'conversation';

Some files were not shown because too many files have changed in this diff Show More