diff --git a/.augment/rules/rules.md b/.augment/rules/rules.md new file mode 100644 index 0000000..8e122c6 --- /dev/null +++ b/.augment/rules/rules.md @@ -0,0 +1,15 @@ +--- +type: "always_apply" +--- + +1.使用中文问回答所有问题; +2.未经允许不可以删除任何文件; +3.所有开发都必须遵循当前现有的项目规范; +4.前端所有接口的访问尽可能走网关调用; +5.为不同(local,dev,prod)环境创建单独的配置文件,可以在部署时通过参数选择要使用的配置文件; +6.Controller层不允许写业务代码,业务代码写在service层中; +7.除了特殊情况下的异常,一般情况下不需要try-catch,由全局异常处理机制处理 +8.所有Controller层接口定义要完整,入参使用request封装请求,出参是response封装出参,使用项目已有的Result做接口返回 +9.所有对项目的变更要遵循当前的项目现有规范 +10.禁止在新增的Controller层的路由前面添加/api +11.与当前用户相关的接口,禁止直接传递用户id,需要后端根据当前登录用户,接口调用的token获取当前登录的用户信息 \ No newline at end of file diff --git a/.env b/.env deleted file mode 100644 index 0287730..0000000 --- a/.env +++ /dev/null @@ -1,18 +0,0 @@ -# 数据库配置 -MYSQL_ROOT_PASSWORD=EmotionMuseum2025*# -MYSQL_DATABASE=emotion_museum -MYSQL_USER=root -MYSQL_PASSWORD=EmotionMuseum2025# - -# Redis配置 -REDIS_PASSWORD= - -# Nacos配置 -NACOS_AUTH_ENABLE=false - -# 应用配置 -SPRING_PROFILES_ACTIVE=docker -TZ=Asia/Shanghai - -# Coze API配置 (与开发环境一致) -COZE_API_TOKEN=pat_GCR4qKzqpf90wMCvKsldMrB18KG3QsLDci65bZthssKsbLxu8X70BKYumleDcabO diff --git a/.env.prod b/.env.prod deleted file mode 100644 index b2beccc..0000000 --- a/.env.prod +++ /dev/null @@ -1,38 +0,0 @@ -# 情绪博物馆生产环境配置文件 -# 用于Docker Compose和部署脚本 - -# 基础配置 -TZ=Asia/Shanghai -APP_VERSION=1.0.0 -SPRING_PROFILES_ACTIVE=prod - -# 服务器配置 -SERVER_HOST=47.111.10.27 -SERVER_USER=root -SERVER_IP=47.111.10.27 - -# 数据库配置 -MYSQL_ROOT_PASSWORD=123456 -MYSQL_DATABASE=emotion_museum -MYSQL_USER=emotion -MYSQL_PASSWORD=EmotionDB2024! -MYSQL_HOST=localhost -MYSQL_PORT=3306 - -# Redis配置 -REDIS_HOST=localhost -REDIS_PORT=6379 - -# Nacos配置 -NACOS_SERVER_ADDR=localhost:8848 -NACOS_AUTH_ENABLE=false - -# Coze API配置 -COZE_API_TOKEN=pat_GCR4qKzqpf90wMCvKsldMrB18KG3QsLDci65bZthssKsbLxu8X70BKYumleDcabO - -# 目录配置 -REMOTE_BASE_DIR=/data -REMOTE_BUILDS_DIR=/data/builds -REMOTE_WEB_DIR=/data/www/emotion-museum/web -REMOTE_LOGS_DIR=/data/logs/emotion-museum -REMOTE_PROGRAMS_DIR=/data/programs diff --git a/.idea/AugmentWebviewStateStore.xml b/.idea/AugmentWebviewStateStore.xml index 5c45b02..49d2460 100644 --- a/.idea/AugmentWebviewStateStore.xml +++ b/.idea/AugmentWebviewStateStore.xml @@ -3,7 +3,7 @@ diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 234afca..60e9baa 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -16,40 +16,19 @@ - - - + + + + - - - - - - - - - - - - - - - - - - - - - - diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index ba94529..6a31e7e 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -13,5 +13,20 @@ $ProjectFileDir$ + + mysql.8 + true + true + $PROJECT_DIR$/backend/emotion-websocket/src/main/resources/application-prod.yml + com.mysql.cj.jdbc.Driver + jdbc:mysql://47.111.10.27:3306/?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT + + + + + + + $ProjectFileDir$ + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml index ae3bac1..c9d7e2c 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -3,6 +3,8 @@ + + @@ -19,6 +21,8 @@ + + diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml index 91d7a08..59ac270 100644 --- a/.idea/sqldialects.xml +++ b/.idea/sqldialects.xml @@ -1,6 +1,7 @@ + \ No newline at end of file diff --git a/backend-single/deploy-server.sh b/backend-single/deploy-server.sh new file mode 100755 index 0000000..58db44d --- /dev/null +++ b/backend-single/deploy-server.sh @@ -0,0 +1,230 @@ +#!/bin/bash + +# 情绪博物馆后端服务部署脚本 +# 使用生产环境配置,日志保存到 /data/logs/emotion-museum/single + +set -e + +# 配置变量 +APP_NAME="emotion-museum-single" +JAR_NAME="emotion-single-1.0.0.jar" +JAR_PATH="./${JAR_NAME}" +LOG_DIR="./logs/" +PID_FILE="/tmp/${APP_NAME}.pid" +JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOG_DIR}/heapdump.hprof" + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# 日志函数 +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# 检查 jar 文件是否存在 +check_jar() { + if [ ! -f "$JAR_PATH" ]; then + log_error "JAR 文件不存在: $JAR_PATH" + log_info "请先执行打包命令: mvn clean package -Pprod" + exit 1 + fi + log_info "JAR 文件检查通过: $JAR_PATH" +} + +# 创建日志目录 +create_log_dir() { + if [ ! -d "$LOG_DIR" ]; then + log_info "创建日志目录: $LOG_DIR" + mkdir -p "$LOG_DIR" + fi +} + +# 停止旧服务 +stop_service() { + if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if ps -p "$PID" > /dev/null 2>&1; then + log_info "停止旧服务 (PID: $PID)" + kill "$PID" + + # 等待服务停止 + for i in {1..30}; do + if ! ps -p "$PID" > /dev/null 2>&1; then + log_info "服务已停止" + break + fi + sleep 1 + done + + # 强制停止 + if ps -p "$PID" > /dev/null 2>&1; then + log_warn "强制停止服务 (PID: $PID)" + kill -9 "$PID" + fi + else + log_warn "PID 文件存在但进程不存在,清理 PID 文件" + fi + rm -f "$PID_FILE" + else + log_info "没有找到 PID 文件,服务可能未运行" + fi +} + +# 启动新服务 +start_service() { + log_info "启动新服务..." + + # 启动命令 + nohup java $JAVA_OPTS \ + -Dspring.profiles.active=prod \ + -Dlogging.file.path=$LOG_DIR \ + -Dlogging.file.name=$LOG_DIR/application.log \ + -jar "$JAR_PATH" \ + > "$LOG_DIR/startup.log" 2>&1 & + + # 保存 PID + echo $! > "$PID_FILE" + + log_info "服务启动中,PID: $(cat $PID_FILE)" + log_info "启动日志: $LOG_DIR/startup.log" + log_info "应用日志: $LOG_DIR/application.log" +} + +# 检查服务状态 +check_status() { + if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if ps -p "$PID" > /dev/null 2>&1; then + log_info "服务运行中 (PID: $PID)" + return 0 + else + log_error "PID 文件存在但进程不存在" + return 1 + fi + else + log_error "PID 文件不存在,服务未运行" + return 1 + fi +} + +# 等待服务启动 +wait_for_startup() { + log_info "等待服务启动..." + for i in {1..60}; do + if check_status > /dev/null 2>&1; then + # 检查端口是否监听(假设使用 8080 端口) + if netstat -tlnp 2>/dev/null | grep -q ":8080.*LISTEN"; then + log_info "服务启动成功!" + return 0 + fi + fi + sleep 2 + done + + log_error "服务启动超时,请检查日志: $LOG_DIR/startup.log" + return 1 +} + +# 显示服务信息 +show_info() { + log_info "=== 服务信息 ===" + log_info "应用名称: $APP_NAME" + log_info "JAR 文件: $JAR_PATH" + log_info "日志目录: $LOG_DIR" + log_info "PID 文件: $PID_FILE" + log_info "Java 参数: $JAVA_OPTS" + + if check_status > /dev/null 2>&1; then + PID=$(cat "$PID_FILE") + log_info "服务状态: 运行中 (PID: $PID)" + + # 显示内存使用情况 + if command -v jstat > /dev/null 2>&1; then + log_info "内存使用情况:" + jstat -gc "$PID" | head -2 + fi + else + log_info "服务状态: 未运行" + fi +} + +# 主函数 +main() { + log_info "开始部署 $APP_NAME 服务..." + + # 检查 jar 文件 + check_jar + + # 创建日志目录 + create_log_dir + + # 停止旧服务 + stop_service + + # 启动新服务 + start_service + + # 等待启动 + if wait_for_startup; then + log_info "部署成功!" + show_info + else + log_error "部署失败!" + exit 1 + fi +} + +# 处理命令行参数 +case "${1:-deploy}" in + "deploy") + main + ;; + "start") + check_jar + create_log_dir + start_service + wait_for_startup + ;; + "stop") + stop_service + ;; + "restart") + stop_service + sleep 2 + check_jar + create_log_dir + start_service + wait_for_startup + ;; + "status") + show_info + ;; + "logs") + if [ -f "$LOG_DIR/application.log" ]; then + tail -f "$LOG_DIR/application.log" + else + log_error "日志文件不存在: $LOG_DIR/application.log" + fi + ;; + *) + echo "用法: $0 {deploy|start|stop|restart|status|logs}" + echo " deploy - 部署服务(默认)" + echo " start - 启动服务" + echo " stop - 停止服务" + echo " restart - 重启服务" + echo " status - 查看服务状态" + echo " logs - 查看实时日志" + exit 1 + ;; +esac \ No newline at end of file diff --git a/backend-single/deploy.sh b/backend-single/deploy.sh index 98ec1aa..8596bb1 100755 --- a/backend-single/deploy.sh +++ b/backend-single/deploy.sh @@ -1,245 +1,4 @@ #!/bin/bash - -# 单体服务部署脚本 -# 作者: emotion-museum -# 日期: 2025-07-21 - set -e - -REMOTE_HOST="root@47.111.10.27" -REMOTE_JAR_DIR="/data/builds" -REMOTE_LOG_DIR="/data/logs/emotion-museum" -SERVICE_NAME="emotion-single" -JAR_FILE="target/emotion-single-1.0.0.jar" - -# 颜色输出 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -log_info() { - echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -# 检查JAR文件 -check_jar() { - log_info "检查JAR文件..." - if [ ! -f "$JAR_FILE" ]; then - log_error "JAR文件不存在: $JAR_FILE" - log_info "请先运行: ./build.sh" - exit 1 - fi - log_success "JAR文件存在: $JAR_FILE" -} - -# 检查SSH连接 -check_connection() { - log_info "检查远程服务器连接..." - if ssh -o ConnectTimeout=10 "$REMOTE_HOST" "echo 'SSH连接成功'" > /dev/null 2>&1; then - log_success "远程服务器连接正常" - else - log_error "无法连接到远程服务器: $REMOTE_HOST" - exit 1 - fi -} - -# 停止旧的微服务 -stop_old_services() { - log_info "停止旧的微服务..." - - ssh "$REMOTE_HOST" " - echo '🛑 停止所有emotion相关的Java进程...' - - # 停止所有emotion相关的Java进程 - pkill -f 'emotion-.*\.jar' 2>/dev/null || echo '没有找到emotion相关进程' - - # 等待进程完全停止 - sleep 5 - - # 检查是否还有残留进程 - REMAINING=\$(ps aux | grep -E 'emotion-.*\.jar' | grep -v grep | wc -l) - if [ \$REMAINING -gt 0 ]; then - echo '强制停止残留进程...' - pkill -9 -f 'emotion-.*\.jar' 2>/dev/null || true - sleep 3 - fi - - echo '✅ 旧服务停止完成' - " - - log_success "旧的微服务已停止" -} - -# 清理旧的部署文件 -cleanup_old_files() { - log_info "清理旧的部署文件..." - - ssh "$REMOTE_HOST" " - echo '🧹 清理旧的JAR文件...' - rm -f $REMOTE_JAR_DIR/emotion-*.jar 2>/dev/null || true - - echo '🧹 清理旧的日志文件...' - find $REMOTE_LOG_DIR -name '*.log' -mtime +7 -delete 2>/dev/null || true - - echo '✅ 清理完成' - " - - log_success "旧的部署文件已清理" -} - -# 创建必要目录 -create_directories() { - log_info "创建必要目录..." - - ssh "$REMOTE_HOST" " - mkdir -p $REMOTE_JAR_DIR - mkdir -p $REMOTE_LOG_DIR - mkdir -p /data/uploads/emotion-museum - - echo '✅ 目录创建完成' - " - - log_success "必要目录已创建" -} - -# 上传JAR文件 -upload_jar() { - log_info "上传JAR文件..." - - scp "$JAR_FILE" "$REMOTE_HOST:$REMOTE_JAR_DIR/" - - # 验证上传 - REMOTE_SIZE=$(ssh "$REMOTE_HOST" "ls -lh $REMOTE_JAR_DIR/emotion-single-1.0.0.jar | awk '{print \$5}'") - log_success "JAR文件上传成功 (远程大小: $REMOTE_SIZE)" -} - -# 启动服务 -start_service() { - log_info "启动单体服务..." - - ssh "$REMOTE_HOST" " - cd $REMOTE_JAR_DIR - - echo '🚀 启动emotion-single服务...' - nohup java -Xms512m -Xmx1024m \\ - -Dspring.profiles.active=prod \\ - -Dserver.port=8080 \\ - -jar emotion-single-1.0.0.jar \\ - > $REMOTE_LOG_DIR/emotion-single.log 2>&1 & - - echo '⏳ 等待服务启动...' - sleep 15 - - # 检查进程 - if pgrep -f emotion-single-1.0.0.jar > /dev/null; then - echo '✅ 服务进程启动成功' - else - echo '❌ 服务进程启动失败' - tail -20 $REMOTE_LOG_DIR/emotion-single.log - exit 1 - fi - - # 检查端口 - if netstat -tlnp | grep :8080 > /dev/null; then - echo '✅ 服务端口8080正在监听' - else - echo '⚠️ 服务端口8080未监听,可能还在启动中' - fi - " - - log_success "单体服务启动完成" -} - -# 健康检查 -health_check() { - log_info "执行健康检查..." - - # 等待服务完全启动 - sleep 10 - - # 检查健康端点 - if curl -f -s "http://47.111.10.27:8080/api/health" > /dev/null; then - log_success "✅ 健康检查通过: http://47.111.10.27:8080/api/health" - else - log_warning "⚠️ 健康检查失败,查看日志..." - ssh "$REMOTE_HOST" "tail -20 $REMOTE_LOG_DIR/emotion-single.log" - fi -} - -# 显示服务状态 -show_status() { - log_info "显示服务状态..." - - ssh "$REMOTE_HOST" " - echo '📊 服务状态:' - echo '==================' - - # 检查进程 - echo -n 'emotion-single进程: ' - if pgrep -f emotion-single-1.0.0.jar > /dev/null; then - echo '✅ 运行中' - ps aux | grep emotion-single-1.0.0.jar | grep -v grep | head -1 - else - echo '❌ 未运行' - fi - - # 检查端口 - echo -n 'emotion-single端口(8080): ' - if netstat -tlnp | grep :8080 > /dev/null; then - echo '✅ 监听中' - else - echo '❌ 未监听' - fi - - echo '' - echo '📋 访问地址:' - echo ' 健康检查: http://47.111.10.27:8080/api/health' - echo ' 前端页面: http://47.111.10.27/emotion/happy/' - " - - log_success "服务状态检查完成" -} - -# 主函数 -main() { - log_info "🚀 开始部署emotion-single服务..." - - check_jar - check_connection - stop_old_services - cleanup_old_files - create_directories - upload_jar - start_service - health_check - show_status - - log_success "🎉 部署完成!" - echo "" - echo "📋 部署结果:" - echo " ✅ 单体服务已启动" - echo " ✅ 旧的微服务已清理" - echo " ✅ 服务正常运行" - echo "" - echo "🔧 下一步:" - echo " 1. 访问前端: http://47.111.10.27/emotion/happy/" - echo " 2. 测试API: http://47.111.10.27:8080/api/health" - echo " 3. 查看日志: ssh $REMOTE_HOST 'tail -f $REMOTE_LOG_DIR/emotion-single.log'" -} - -# 执行主函数 -main "$@" +mvn clean package -Pprod +echo "后端已打包,target 目录下的 jar 包可部署到服务器" diff --git a/backend-single/pom.xml b/backend-single/pom.xml index b3fb2cf..f7d8fc4 100644 --- a/backend-single/pom.xml +++ b/backend-single/pom.xml @@ -179,10 +179,10 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.11.0 - 8 - 8 + 17 + 17 UTF-8 diff --git a/backend-single/src/main/java/com/emotion/controller/EmotionRecordController.java b/backend-single/src/main/java/com/emotion/controller/EmotionRecordController.java index 7453678..55fd466 100644 --- a/backend-single/src/main/java/com/emotion/controller/EmotionRecordController.java +++ b/backend-single/src/main/java/com/emotion/controller/EmotionRecordController.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.emotion.common.Result; import com.emotion.entity.EmotionRecord; import com.emotion.service.EmotionRecordService; +import com.emotion.util.CurrentUserUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -11,7 +12,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; - import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; @@ -23,7 +23,7 @@ import java.util.*; * @date 2025-07-22 */ @RestController -@RequestMapping("/api/emotion-records") +@RequestMapping("/emotion-records") @Tag(name = "情绪记录管理", description = "用户情绪记录的增删改查功能") public class EmotionRecordController { @@ -64,16 +64,18 @@ public class EmotionRecordController { /** * 获取用户情绪记录列表 */ - @Operation(summary = "获取用户情绪记录列表", description = "分页获取指定用户的情绪记录,按创建时间倒序") - @GetMapping("/user/{userId}") + @Operation(summary = "获取用户情绪记录列表", description = "分页获取当前用户的情绪记录,按创建时间倒序") + @GetMapping("/user") public Result> getRecordList( - @Parameter(description = "用户ID") @PathVariable String userId, @Parameter(description = "页码,从1开始") @RequestParam(defaultValue = "1") Integer current, @Parameter(description = "每页大小") @RequestParam(defaultValue = "10") Integer size) { - log.info("获取用户情绪记录列表: userId={}, current={}, size={}", userId, current, size); - try { + // 从上下文中获取当前用户ID + String userId = CurrentUserUtil.requireCurrentUserId(); + + log.info("获取用户情绪记录列表: userId={}, current={}, size={}", userId, current, size); + IPage page = emotionRecordService.getByUserIdWithPage(userId, current, size); log.info("获取用户情绪记录成功: userId={}, total={}, records={}", @@ -81,12 +83,44 @@ public class EmotionRecordController { return Result.success(page); + } catch (IllegalStateException e) { + log.warn("用户认证失败: {}", e.getMessage()); + return Result.error(e.getMessage()); } catch (Exception e) { - log.error("获取用户情绪记录失败: userId={}", userId, e); + log.error("获取用户情绪记录失败", e); return Result.error("获取情绪记录失败: " + e.getMessage()); } } + /** + * 获取用户最近情绪记录 + */ + @Operation(summary = "获取用户最近情绪记录", description = "获取当前用户最近的情绪记录列表") + @GetMapping("/user/recent") + public Result> getRecentRecords( + @Parameter(description = "限制数量") @RequestParam(defaultValue = "5") Integer limit) { + + try { + // 从上下文中获取当前用户ID + String userId = CurrentUserUtil.requireCurrentUserId(); + + log.info("获取用户最近情绪记录: userId={}, limit={}", userId, limit); + + List records = emotionRecordService.getRecentByUserId(userId, limit); + + log.info("获取用户最近情绪记录成功: userId={}, records={}", userId, records.size()); + + return Result.success(records); + + } catch (IllegalStateException e) { + log.warn("用户认证失败: {}", e.getMessage()); + return Result.error(e.getMessage()); + } catch (Exception e) { + log.error("获取用户最近情绪记录失败", e); + return Result.error("获取最近记录失败: " + e.getMessage()); + } + } + /** * 获取情绪记录详情 */ diff --git a/backend-single/src/main/java/com/emotion/controller/EmotionSummaryController.java b/backend-single/src/main/java/com/emotion/controller/EmotionSummaryController.java index 4e7c5dc..64ba69e 100644 --- a/backend-single/src/main/java/com/emotion/controller/EmotionSummaryController.java +++ b/backend-single/src/main/java/com/emotion/controller/EmotionSummaryController.java @@ -2,8 +2,8 @@ package com.emotion.controller; import com.emotion.common.Result; import com.emotion.service.AIChatService; +import com.emotion.util.CurrentUserUtil; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -13,13 +13,13 @@ import java.util.Map; /** * 情绪总结控制器 - * + * * @author emotion-museum * @date 2025-07-25 */ @Slf4j @RestController -@RequestMapping("/api/emotion-summary") +@RequestMapping("/emotion-summary") @Tag(name = "情绪总结管理", description = "用户情绪记录总结和分析功能") public class EmotionSummaryController { @@ -27,39 +27,46 @@ public class EmotionSummaryController { private AIChatService aiChatService; @Operation(summary = "生成用户当天的情绪记录总结", description = "基于用户当天的聊天记录生成情绪分析和记录") - @PostMapping("/generate/{userId}") - public Result> generateEmotionSummary( - @Parameter(description = "用户ID") @PathVariable String userId) { - - log.info("收到生成情绪记录总结请求: userId={}", userId); - + @PostMapping("/generate") + public Result> generateEmotionSummary() { + try { + // 从上下文中获取当前用户ID + String userId = CurrentUserUtil.requireCurrentUserId(); + + log.info("收到生成情绪记录总结请求: userId={}", userId); + // 调用AI服务生成情绪总结 Map result = aiChatService.generateEmotionSummary(userId); - + if ((Boolean) result.get("success")) { log.info("情绪记录总结生成成功: userId={}", userId); - return Result.success(result, "情绪记录总结生成成功"); + return Result.success("情绪记录总结生成成功", result); } else { String message = (String) result.get("message"); log.warn("情绪记录总结生成失败: userId={}, message={}", userId, message); return Result.error(message); } - + + } catch (IllegalStateException e) { + log.warn("用户认证失败: {}", e.getMessage()); + return Result.error(e.getMessage()); } catch (Exception e) { - log.error("生成情绪记录总结时发生异常: userId={}", userId, e); + log.error("生成情绪记录总结时发生异常", e); return Result.error("生成情绪记录总结失败: " + e.getMessage()); } } @Operation(summary = "获取用户情绪记录总结状态", description = "检查用户今天是否已经生成过情绪记录") - @GetMapping("/status/{userId}") - public Result> getEmotionSummaryStatus( - @Parameter(description = "用户ID") @PathVariable String userId) { - - log.info("查询用户情绪记录总结状态: userId={}", userId); - + @GetMapping("/status") + public Result> getEmotionSummaryStatus() { + try { + // 从上下文中获取当前用户ID + String userId = CurrentUserUtil.requireCurrentUserId(); + + log.info("查询用户情绪记录总结状态: userId={}", userId); + // 这里可以添加检查用户今天是否已经生成过情绪记录的逻辑 // 暂时返回基本状态信息 Map status = Map.of( @@ -67,11 +74,14 @@ public class EmotionSummaryController { "canGenerate", true, "message", "可以生成情绪记录总结" ); - + return Result.success(status); - + + } catch (IllegalStateException e) { + log.warn("用户认证失败: {}", e.getMessage()); + return Result.error(e.getMessage()); } catch (Exception e) { - log.error("查询情绪记录总结状态时发生异常: userId={}", userId, e); + log.error("查询情绪记录总结状态时发生异常", e); return Result.error("查询状态失败: " + e.getMessage()); } } diff --git a/backend-single/src/main/java/com/emotion/controller/MessageController.java b/backend-single/src/main/java/com/emotion/controller/MessageController.java index b37f6f3..14daeb5 100644 --- a/backend-single/src/main/java/com/emotion/controller/MessageController.java +++ b/backend-single/src/main/java/com/emotion/controller/MessageController.java @@ -8,6 +8,7 @@ import com.emotion.dto.request.MessageCreateRequest; import com.emotion.dto.response.MessageResponse; import com.emotion.entity.Message; import com.emotion.service.MessageService; +import com.emotion.util.CurrentUserUtil; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -126,36 +127,76 @@ public class MessageController { /** * 根据用户ID分页查询消息 */ - @GetMapping("/user/{userId}/page") - public Result> getPageByUserId(@PathVariable String userId, - @Valid PageRequest request) { - IPage page = messageService.getByUserIdWithPage(userId, Math.toIntExact(request.getCurrent()), Math.toIntExact(request.getSize())); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); + @GetMapping("/user/page") + public Result> getPageByUserId(@Valid PageRequest request) { - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); + try { + // 从上下文中获取当前用户ID + String userId = CurrentUserUtil.requireCurrentUserId(); - return Result.success(pageResult); + IPage page = messageService.getByUserIdWithPage(userId, Math.toIntExact(request.getCurrent()), + Math.toIntExact(request.getSize())); + List responses = page.getRecords().stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + + PageResult pageResult = new PageResult<>(); + pageResult.setCurrent(page.getCurrent()); + pageResult.setSize(page.getSize()); + pageResult.setTotal(page.getTotal()); + pageResult.setPages(page.getPages()); + pageResult.setRecords(responses); + + return Result.success(pageResult); + + } catch (IllegalStateException e) { + return Result.error(e.getMessage()); + } } /** * 根据用户ID和关键词搜索消息 */ - @GetMapping("/user/{userId}/search") - public Result> searchByUserId(@PathVariable String userId, + @GetMapping("/user/search") + public Result> searchByUserId( @RequestParam String keyword, @RequestParam(defaultValue = "50") Integer limit) { - List messages = messageService.searchByUserIdAndKeyword(userId, keyword, limit); - List responses = messages.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - return Result.success(responses); + + try { + // 从上下文中获取当前用户ID + String userId = CurrentUserUtil.requireCurrentUserId(); + + List messages = messageService.searchByUserIdAndKeyword(userId, keyword, limit); + List responses = messages.stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + return Result.success(responses); + + } catch (IllegalStateException e) { + return Result.error(e.getMessage()); + } + } + + /** + * 获取用户最近的聊天记录 + */ + @GetMapping("/user/recent") + public Result> getRecentMessages( + @RequestParam(defaultValue = "10") Integer limit) { + + try { + // 从上下文中获取当前用户ID + String userId = CurrentUserUtil.requireCurrentUserId(); + + List messages = messageService.getRecentByUserId(userId, limit); + List responses = messages.stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + return Result.success(responses); + + } catch (IllegalStateException e) { + return Result.error(e.getMessage()); + } } /** diff --git a/backend-single/src/main/java/com/emotion/entity/Message.java b/backend-single/src/main/java/com/emotion/entity/Message.java index 22d1e95..3016536 100644 --- a/backend-single/src/main/java/com/emotion/entity/Message.java +++ b/backend-single/src/main/java/com/emotion/entity/Message.java @@ -145,5 +145,28 @@ public class Message extends BaseEntity { @TableField("metadata") private String metadata; + /** + * 用户ID (注册用户或访客用户) + */ + @TableField("user_id") + private String userId; + + /** + * 用户类型 (registered/guest) + */ + @TableField("user_type") + private String userType; + + /** + * Coze消息角色 (user/assistant/system) + */ + @TableField("coze_role") + private String cozeRole; + + /** + * Coze消息内容类型 (text/image/file等) + */ + @TableField("coze_content_type") + private String cozeContentType; } diff --git a/backend-single/src/main/java/com/emotion/interceptor/JwtAuthInterceptor.java b/backend-single/src/main/java/com/emotion/interceptor/JwtAuthInterceptor.java index fe67a16..f43cda6 100644 --- a/backend-single/src/main/java/com/emotion/interceptor/JwtAuthInterceptor.java +++ b/backend-single/src/main/java/com/emotion/interceptor/JwtAuthInterceptor.java @@ -1,6 +1,7 @@ package com.emotion.interceptor; import com.emotion.util.JwtUtil; +import com.emotion.util.UserContextHolder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -64,19 +65,32 @@ public class JwtAuthInterceptor implements HandlerInterceptor { return false; } - // 从token中获取用户信息并设置到请求属性中 + // 从token中获取用户信息并设置到请求属性和上下文中 String userId = jwtUtil.getUserIdFromToken(token); String username = jwtUtil.getUsernameFromToken(token); - + + // 设置到请求属性中(兼容现有代码) request.setAttribute("userId", userId); request.setAttribute("username", username); request.setAttribute("token", token); - + + // 设置到线程本地上下文中(推荐使用) + UserContextHolder.setCurrentUserId(userId); + UserContextHolder.setCurrentUsername(username); + UserContextHolder.setCurrentToken(token); + log.debug("Token验证成功,用户: {} ({})", username, userId); return true; } + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + // 请求完成后清除用户上下文,防止内存泄漏 + UserContextHolder.clear(); + log.debug("请求完成,已清除用户上下文"); + } + /** * 判断是否为公开接口(不需要认证) */ @@ -88,11 +102,8 @@ public class JwtAuthInterceptor implements HandlerInterceptor { "/api/auth/captcha", "/api/auth/refresh-token", "/api/health", - "/api/ws/chat", - "/api/emotion-records", // 情绪记录接口 - "/api/emotion-summary", // 情绪总结接口 - "/message", // 消息接口 - "/swagger-ui", + "/api/ws/chat", + "/swagger-ui", "/v3/api-docs", "/actuator" }; diff --git a/backend-single/src/main/java/com/emotion/mapper/MessageMapper.java b/backend-single/src/main/java/com/emotion/mapper/MessageMapper.java index a788caf..8229a2c 100644 --- a/backend-single/src/main/java/com/emotion/mapper/MessageMapper.java +++ b/backend-single/src/main/java/com/emotion/mapper/MessageMapper.java @@ -68,4 +68,16 @@ public interface MessageMapper extends BaseMapper { List searchByUserIdAndKeyword(@Param("userId") String userId, @Param("keyword") String keyword, @Param("limit") Integer limit); + + /** + * 根据用户ID获取最近的消息 + */ + @Select("SELECT m.* FROM message m " + + "INNER JOIN conversation c ON m.conversation_id = c.id " + + "WHERE c.user_id = #{userId} " + + "AND m.is_deleted = 0 " + + "ORDER BY m.create_time DESC " + + "LIMIT #{limit}") + List getRecentByUserId(@Param("userId") String userId, + @Param("limit") Integer limit); } diff --git a/backend-single/src/main/java/com/emotion/service/MessageService.java b/backend-single/src/main/java/com/emotion/service/MessageService.java index 08a66fe..aec91c7 100644 --- a/backend-single/src/main/java/com/emotion/service/MessageService.java +++ b/backend-single/src/main/java/com/emotion/service/MessageService.java @@ -55,7 +55,12 @@ public interface MessageService extends IService { * 根据用户ID和关键词搜索消息 */ List searchByUserIdAndKeyword(String userId, String keyword, Integer limit); - + + /** + * 根据用户ID获取最近的消息 + */ + List getRecentByUserId(String userId, Integer limit); + /** * 查询会话的最后一条消息 */ diff --git a/backend-single/src/main/java/com/emotion/service/WebSocketService.java b/backend-single/src/main/java/com/emotion/service/WebSocketService.java index f1e8d43..13a250d 100644 --- a/backend-single/src/main/java/com/emotion/service/WebSocketService.java +++ b/backend-single/src/main/java/com/emotion/service/WebSocketService.java @@ -4,6 +4,7 @@ import com.emotion.dto.websocket.ChatRequest; import com.emotion.dto.websocket.ConnectRequest; import com.emotion.dto.websocket.WebSocketMessage; import com.emotion.entity.Message; +import com.emotion.entity.Conversation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.simp.SimpMessagingTemplate; @@ -219,50 +220,84 @@ public class WebSocketService { // 使用线程池异步处理AI响应 new Thread(() -> { try { + String userId = request.getSenderId(); + String conversationId = request.getConversationId(); + + // 如果没有会话ID,创建新会话 + if (conversationId == null || conversationId.trim().isEmpty()) { + conversationId = createNewConversation(userId, request); + request.setConversationId(conversationId); + } + + // 确保会话存在并更新活跃时间 + ensureConversationExists(conversationId, userId, request); + // 保存用户消息到数据库 Message userMessage = new Message(); - userMessage.setConversationId(request.getConversationId()); - userMessage.setCreateBy(request.getSenderId()); + userMessage.setConversationId(conversationId); + userMessage.setUserId(userId); + userMessage + .setUserType(request.getSenderType() == ChatRequest.SenderType.USER ? "registered" : "guest"); userMessage.setContent(request.getContent()); - userMessage.setType(request.getMessageType().name()); - userMessage.setSender(request.getSenderType().name()); + userMessage.setType("text"); + userMessage.setSender("user"); + userMessage.setCozeRole("user"); + userMessage.setCozeContentType("text"); messageService.createMessage(userMessage); // 调用AI服务 String aiReply = aiChatService.sendChatMessage( - request.getConversationId(), - request.getContent(), - request.getSenderId() + conversationId, + request.getContent(), + userId ); - - // 构建AI回复消息 - WebSocketMessage aiMessage = WebSocketMessage.builder() - .messageId(UUID.randomUUID().toString()) - .conversationId(request.getConversationId()) - .type(WebSocketMessage.MessageType.TEXT) - .content(aiReply) - .senderId("ai") - .senderType(WebSocketMessage.SenderType.AI) - .status(WebSocketMessage.MessageStatus.SENT) - .createTime(LocalDateTime.now()) - .build(); - - // 保存AI回复到数据库 - Message aiDbMessage = new Message(); - aiDbMessage.setConversationId(request.getConversationId()); - aiDbMessage.setCreateBy("ai"); - aiDbMessage.setContent(aiReply); - aiDbMessage.setType("text"); - aiDbMessage.setSender("ai"); - messageService.createMessage(aiDbMessage); - // 发送AI回复 - messagingTemplate.convertAndSendToUser(request.getSenderId(), "/queue/messages", aiMessage); + // 如果AI回复包含换行符,分割成多条消息 + String[] replyParts = aiReply.split("\\n\\n|\\n"); - if (request.getConversationId() != null) { - messagingTemplate.convertAndSend("/topic/conversation/" + request.getConversationId(), aiMessage); + for (String part : replyParts) { + if (part.trim().isEmpty()) + continue; + + // 构建AI回复消息 + WebSocketMessage aiMessage = WebSocketMessage.builder() + .messageId(UUID.randomUUID().toString()) + .conversationId(conversationId) + .type(WebSocketMessage.MessageType.TEXT) + .content(part.trim()) + .senderId("ai") + .senderType(WebSocketMessage.SenderType.AI) + .status(WebSocketMessage.MessageStatus.SENT) + .createTime(LocalDateTime.now()) + .build(); + + // 保存AI回复到数据库 + Message aiDbMessage = new Message(); + aiDbMessage.setConversationId(conversationId); + aiDbMessage.setUserId(userId); + aiDbMessage.setUserType( + request.getSenderType() == ChatRequest.SenderType.USER ? "registered" : "guest"); + aiDbMessage.setContent(part.trim()); + aiDbMessage.setType("text"); + aiDbMessage.setSender("ai"); + aiDbMessage.setCozeRole("assistant"); + aiDbMessage.setCozeContentType("text"); + messageService.createMessage(aiDbMessage); + + // 发送AI回复 + messagingTemplate.convertAndSendToUser(userId, "/queue/messages", aiMessage); + + if (conversationId != null) { + messagingTemplate.convertAndSend("/topic/conversation/" + conversationId, aiMessage); + } + + // 添加短暂延迟,模拟自然对话 + Thread.sleep(500); } - + + // 更新会话的最后活跃时间和消息数量 + updateConversationActivity(conversationId); + } catch (Exception e) { log.error("AI响应处理失败", e); sendErrorMessage(request.getSenderId(), "AI服务暂时不可用,请稍后重试"); @@ -293,4 +328,85 @@ public class WebSocketService { public int getOnlineUserCount() { return onlineUsers.size(); } + + /** + * 创建新会话 + */ + private String createNewConversation(String userId, ChatRequest request) { + try { + String conversationId = "conv_" + System.currentTimeMillis() + "_" + UUID.randomUUID().toString().substring(0, 8); + + Conversation conversation = Conversation.builder() + .userId(userId) + .userType(request.getSenderType() == ChatRequest.SenderType.USER ? "registered" : "guest") + .title("新对话") + .type("chat") + .conversationStatus("active") + .startTime(LocalDateTime.now()) + .lastActiveTime(LocalDateTime.now()) + .messageCount(0) + .build(); + + // 设置ID + conversation.setId(conversationId); + + conversationService.save(conversation); + log.info("创建新会话: conversationId={}, userId={}", conversationId, userId); + + return conversationId; + } catch (Exception e) { + log.error("创建新会话失败: userId={}", userId, e); + throw new RuntimeException("创建会话失败", e); + } + } + + /** + * 确保会话存在并更新活跃时间 + */ + private void ensureConversationExists(String conversationId, String userId, ChatRequest request) { + try { + Conversation conversation = conversationService.getById(conversationId); + if (conversation == null) { + // 如果会话不存在,创建一个 + conversation = Conversation.builder() + .userId(userId) + .userType(request.getSenderType() == ChatRequest.SenderType.USER ? "registered" : "guest") + .title("对话") + .type("chat") + .conversationStatus("active") + .startTime(LocalDateTime.now()) + .lastActiveTime(LocalDateTime.now()) + .messageCount(0) + .build(); + + // 设置ID + conversation.setId(conversationId); + + conversationService.save(conversation); + log.info("创建会话: conversationId={}, userId={}", conversationId, userId); + } else { + // 更新最后活跃时间 + conversation.setLastActiveTime(LocalDateTime.now()); + conversationService.updateById(conversation); + } + } catch (Exception e) { + log.error("确保会话存在失败: conversationId={}, userId={}", conversationId, userId, e); + } + } + + /** + * 更新会话活跃状态 + */ + private void updateConversationActivity(String conversationId) { + try { + Conversation conversation = conversationService.getById(conversationId); + if (conversation != null) { + conversation.setLastActiveTime(LocalDateTime.now()); + conversation.setMessageCount((conversation.getMessageCount() != null ? conversation.getMessageCount() : 0) + 1); + conversationService.updateById(conversation); + } + } catch (Exception e) { + log.error("更新会话活跃状态失败: conversationId={}", conversationId, e); + } + } } diff --git a/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java b/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java index 55a474d..1041ca8 100644 --- a/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java +++ b/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java @@ -6,11 +6,13 @@ import com.emotion.entity.Message; import com.emotion.entity.Conversation; import com.emotion.entity.CozeApiCall; import com.emotion.entity.EmotionRecord; +import com.emotion.entity.EmotionAnalysis; import com.emotion.service.AIChatService; import com.emotion.service.MessageService; import com.emotion.service.ConversationService; import com.emotion.service.CozeApiCallService; import com.emotion.service.EmotionRecordService; +import com.emotion.service.EmotionAnalysisService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -59,6 +61,9 @@ public class AiChatServiceImpl implements AIChatService { @Autowired private EmotionRecordService emotionRecordService; + @Autowired + private EmotionAnalysisService emotionAnalysisService; + @Value("${emotion.coze.api.token:}") private String cozeApiToken; @@ -941,7 +946,7 @@ public class AiChatServiceImpl implements AIChatService { log.info("情绪分析总结生成完成: {}", emotionSummary); // 解析AI返回的情绪分析结果 - EmotionAnalysisResult analysisResult = parseEmotionSummary(emotionSummary); + EmotionAnalysis analysisResult = parseEmotionSummary(emotionSummary); // 创建情绪记录 EmotionRecord emotionRecord = createEmotionRecord(userId, analysisResult, chatHistory); @@ -1011,37 +1016,39 @@ public class AiChatServiceImpl implements AIChatService { /** * 解析情绪分析总结结果 */ - private EmotionAnalysisResult parseEmotionSummary(String summary) { + private EmotionAnalysis parseEmotionSummary(String summary) { try { // 尝试从AI回复中提取JSON String jsonStr = extractJsonFromSummary(summary); if (jsonStr != null) { JSONObject json = JSON.parseObject(jsonStr); - EmotionAnalysisResult result = new EmotionAnalysisResult(); - result.setPrimaryEmotion(json.getString("primaryEmotion")); - result.setIntensity(json.getDoubleValue("intensity")); - result.setTriggers(json.getString("triggers")); - result.setEmotionTrend(json.getString("emotionTrend")); - result.setSuggestions(json.getString("suggestions")); - result.setSummary(json.getString("summary")); - - return result; + return EmotionAnalysis.builder() + .primaryEmotion(json.getString("primaryEmotion")) + .intensity(BigDecimal.valueOf(json.getDoubleValue("intensity"))) + .keywords(json.getString("triggers")) + .suggestion(json.getString("suggestions")) + .text(summary) + .polarity(determinePolarity(json.getString("primaryEmotion"))) + .confidence(BigDecimal.valueOf(0.85)) + .analysisTime(LocalDateTime.now()) + .build(); } } catch (Exception e) { log.warn("解析情绪分析结果失败,使用默认值: {}", e.getMessage()); } // 如果解析失败,返回默认结果 - EmotionAnalysisResult defaultResult = new EmotionAnalysisResult(); - defaultResult.setPrimaryEmotion("平静"); - defaultResult.setIntensity(0.5); - defaultResult.setTriggers("日常对话"); - defaultResult.setEmotionTrend("相对稳定"); - defaultResult.setSuggestions("保持当前的积极状态"); - defaultResult.setSummary(summary); - - return defaultResult; + return EmotionAnalysis.builder() + .primaryEmotion("平静") + .intensity(BigDecimal.valueOf(0.5)) + .keywords("日常对话") + .suggestion("保持当前的积极状态") + .text(summary) + .polarity("neutral") + .confidence(BigDecimal.valueOf(0.5)) + .analysisTime(LocalDateTime.now()) + .build(); } /** @@ -1063,14 +1070,14 @@ public class AiChatServiceImpl implements AIChatService { /** * 创建情绪记录 */ - private EmotionRecord createEmotionRecord(String userId, EmotionAnalysisResult analysisResult, String chatHistory) { + private EmotionRecord createEmotionRecord(String userId, EmotionAnalysis analysisResult, String chatHistory) { EmotionRecord record = EmotionRecord.builder() .userId(userId) .recordDate(LocalDate.now()) .emotionType(analysisResult.getPrimaryEmotion()) - .intensity(BigDecimal.valueOf(analysisResult.getIntensity())) - .triggers(analysisResult.getTriggers()) - .description(analysisResult.getSummary()) + .intensity(analysisResult.getIntensity()) + .triggers(analysisResult.getKeywords()) + .description(analysisResult.getText()) .notes("基于当天聊天记录自动生成的情绪分析") .tags("AI分析,聊天记录,情绪总结") .build(); @@ -1078,37 +1085,14 @@ public class AiChatServiceImpl implements AIChatService { emotionRecordService.save(record); log.info("情绪记录创建成功: recordId={}", record.getId()); + // 设置情绪分析记录的关联ID并保存 + analysisResult.setUserId(userId); + analysisResult.setMessageId(record.getId()); // 关联到情绪记录ID + + emotionAnalysisService.save(analysisResult); + log.info("情绪分析记录创建成功: analysisId={}", analysisResult.getId()); + return record; } - /** - * 情绪分析结果内部类 - */ - public static class EmotionAnalysisResult { - private String primaryEmotion; - private Double intensity; - private String triggers; - private String emotionTrend; - private String suggestions; - private String summary; - - // Getters and Setters - public String getPrimaryEmotion() { return primaryEmotion; } - public void setPrimaryEmotion(String primaryEmotion) { this.primaryEmotion = primaryEmotion; } - - public Double getIntensity() { return intensity; } - public void setIntensity(Double intensity) { this.intensity = intensity; } - - public String getTriggers() { return triggers; } - public void setTriggers(String triggers) { this.triggers = triggers; } - - public String getEmotionTrend() { return emotionTrend; } - public void setEmotionTrend(String emotionTrend) { this.emotionTrend = emotionTrend; } - - public String getSuggestions() { return suggestions; } - public void setSuggestions(String suggestions) { this.suggestions = suggestions; } - - public String getSummary() { return summary; } - public void setSummary(String summary) { this.summary = summary; } - } } \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/impl/MessageServiceImpl.java b/backend-single/src/main/java/com/emotion/service/impl/MessageServiceImpl.java index 7898141..ffe9925 100644 --- a/backend-single/src/main/java/com/emotion/service/impl/MessageServiceImpl.java +++ b/backend-single/src/main/java/com/emotion/service/impl/MessageServiceImpl.java @@ -195,4 +195,10 @@ public class MessageServiceImpl extends ServiceImpl impl // 通过conversation表关联查询用户的消息,根据关键词搜索 return this.baseMapper.searchByUserIdAndKeyword(userId, keyword, limit); } + + @Override + public List getRecentByUserId(String userId, Integer limit) { + // 获取用户最近的消息,按时间倒序 + return this.baseMapper.getRecentByUserId(userId, limit); + } } \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/util/CurrentUserUtil.java b/backend-single/src/main/java/com/emotion/util/CurrentUserUtil.java new file mode 100644 index 0000000..426f31d --- /dev/null +++ b/backend-single/src/main/java/com/emotion/util/CurrentUserUtil.java @@ -0,0 +1,87 @@ +package com.emotion.util; + +import lombok.extern.slf4j.Slf4j; + +/** + * 当前用户工具类 + * 提供便捷的方法获取当前登录用户信息 + * + * @author emotion-museum + * @date 2025-07-25 + */ +@Slf4j +public class CurrentUserUtil { + + /** + * 获取当前用户ID + * + * @return 当前用户ID,如果未登录则返回null + */ + public static String getCurrentUserId() { + return UserContextHolder.getCurrentUserId(); + } + + /** + * 获取当前用户名 + * + * @return 当前用户名,如果未登录则返回null + */ + public static String getCurrentUsername() { + return UserContextHolder.getCurrentUsername(); + } + + /** + * 获取当前用户Token + * + * @return 当前用户Token,如果未登录则返回null + */ + public static String getCurrentToken() { + return UserContextHolder.getCurrentToken(); + } + + /** + * 检查当前是否有用户登录 + * + * @return 是否有用户登录 + */ + public static boolean isUserLoggedIn() { + return UserContextHolder.hasUserContext(); + } + + /** + * 获取当前用户ID,如果未登录则抛出异常 + * + * @return 当前用户ID + * @throws IllegalStateException 如果用户未登录 + */ + public static String requireCurrentUserId() { + String userId = getCurrentUserId(); + if (userId == null || userId.trim().isEmpty()) { + throw new IllegalStateException("用户未登录或认证失败"); + } + return userId; + } + + /** + * 获取当前用户名,如果未登录则抛出异常 + * + * @return 当前用户名 + * @throws IllegalStateException 如果用户未登录 + */ + public static String requireCurrentUsername() { + String username = getCurrentUsername(); + if (username == null || username.trim().isEmpty()) { + throw new IllegalStateException("用户未登录或认证失败"); + } + return username; + } + + /** + * 获取当前用户上下文摘要信息 + * + * @return 用户上下文摘要 + */ + public static String getContextSummary() { + return UserContextHolder.getContextSummary(); + } +} diff --git a/backend-single/src/main/java/com/emotion/util/UserContextHolder.java b/backend-single/src/main/java/com/emotion/util/UserContextHolder.java index c7a6c8a..0286b7b 100644 --- a/backend-single/src/main/java/com/emotion/util/UserContextHolder.java +++ b/backend-single/src/main/java/com/emotion/util/UserContextHolder.java @@ -37,6 +37,11 @@ public class UserContextHolder { */ private static final ThreadLocal REQUEST_ID_HOLDER = new ThreadLocal<>(); + /** + * Token线程本地变量 + */ + private static final ThreadLocal TOKEN_HOLDER = new ThreadLocal<>(); + /** * 设置当前用户ID * @@ -125,12 +130,31 @@ public class UserContextHolder { /** * 获取请求ID - * + * * @return 请求ID */ public static String getRequestId() { return REQUEST_ID_HOLDER.get(); } + + /** + * 设置当前Token + * + * @param token Token + */ + public static void setCurrentToken(String token) { + TOKEN_HOLDER.set(token); + log.debug("设置当前Token: {}", token != null ? "***" : null); + } + + /** + * 获取当前Token + * + * @return Token + */ + public static String getCurrentToken() { + return TOKEN_HOLDER.get(); + } /** * 设置用户上下文信息 @@ -188,6 +212,13 @@ public class UserContextHolder { REQUEST_ID_HOLDER.remove(); } + /** + * 清除当前Token + */ + public static void clearToken() { + TOKEN_HOLDER.remove(); + } + /** * 清除所有用户上下文信息 */ @@ -197,6 +228,7 @@ public class UserContextHolder { clearUserType(); clearClientIp(); clearRequestId(); + clearToken(); log.debug("清除所有用户上下文信息"); } diff --git a/backend-single/src/main/resources/application-coze-example.yml b/backend-single/src/main/resources/application-coze-example.yml deleted file mode 100644 index 5b89453..0000000 --- a/backend-single/src/main/resources/application-coze-example.yml +++ /dev/null @@ -1,44 +0,0 @@ -# Coze API 配置示例 -# 请根据您的实际情况修改以下配置 - -emotion: - coze: - api: - # Coze API 访问令牌 - 从 https://www.coze.cn/docs/developer_guides/pat 获取 - token: "your-coze-api-token-here" - - # Coze API 基础URL - base-url: "https://api.coze.cn" - - # 聊天相关配置 - chat: - talk: - # 聊天机器人ID - 从您的 Coze 工作空间获取 - bot-id: "your-chat-bot-id-here" - # 工作流ID(可选)- 如果使用工作流模式 - workflow-id: "your-chat-workflow-id-here" - - summary: - # 总结机器人ID(可选)- 如果有专门的总结机器人 - bot-id: "your-summary-bot-id-here" - # 总结工作流ID(可选) - workflow-id: "your-summary-workflow-id-here" - - # 请求超时配置(毫秒) - timeout: 30000 - - # 重试配置 - retry-count: 3 - retry-delay: 1000 - -# 配置说明: -# 1. token: 个人访问令牌,需要在 Coze 平台创建 -# 2. bot-id: 机器人ID,在发布机器人时选择"发布到API"获得 -# 3. workflow-id: 工作流ID,如果使用工作流模式则需要配置 -# 4. base-url: 通常为 https://api.coze.cn,国际版可能不同 -# 5. 确保机器人已发布并启用API访问 - -# 重要提醒: -# - 请勿将真实的 token 和 ID 提交到版本控制系统 -# - 建议使用环境变量或配置中心管理敏感信息 -# - 测试时可以先使用简单的聊天机器人验证配置 diff --git a/backend-single/src/main/resources/sql/mysql_emotion_museum_final.sql b/backend-single/src/main/resources/sql/mysql_emotion_museum_final.sql index 815b7d2..eee3f29 100644 --- a/backend-single/src/main/resources/sql/mysql_emotion_museum_final.sql +++ b/backend-single/src/main/resources/sql/mysql_emotion_museum_final.sql @@ -74,39 +74,39 @@ DROP TABLE IF EXISTS user; -- 1. 用户表 (user) -- ============================================================================ CREATE TABLE user ( - id VARCHAR(36) PRIMARY KEY, -- UUID主键 - account VARCHAR(50) UNIQUE, -- 账号 - password VARCHAR(255) , -- 密码(加密后) - username VARCHAR(50) UNIQUE, -- 用户名 - email VARCHAR(100) UNIQUE, -- 邮箱 - phone VARCHAR(20) UNIQUE, -- 手机号 - avatar VARCHAR(500), -- 头像URL - nickname VARCHAR(50) , -- 昵称 - birth_date DATE, -- 生日 - location VARCHAR(100), -- 所在地 - bio TEXT, -- 个人简介 - member_level VARCHAR(20) DEFAULT 'free', -- 会员等级 - total_days INT DEFAULT 0, -- 使用天数 + id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + account VARCHAR(50) UNIQUE COMMENT '账号', -- 账号 + password VARCHAR(255) COMMENT '密码(加密后)', -- 密码(加密后) + username VARCHAR(50) UNIQUE COMMENT '用户名', -- 用户名 + email VARCHAR(100) UNIQUE COMMENT '邮箱', -- 邮箱 + phone VARCHAR(20) UNIQUE COMMENT '手机号', -- 手机号 + avatar VARCHAR(500) COMMENT '头像URL', -- 头像URL + nickname VARCHAR(50) COMMENT '昵称', -- 昵称 + birth_date DATE COMMENT '生日', -- 生日 + location VARCHAR(100) COMMENT '所在地', -- 所在地 + bio TEXT COMMENT '个人简介', -- 个人简介 + member_level VARCHAR(20) DEFAULT 'free' COMMENT '会员等级', -- 会员等级 + total_days INT DEFAULT 0 COMMENT '使用天数', -- 使用天数 -- 成长数据 - self_awareness DECIMAL(5, 2) DEFAULT 50.00, -- 自我感知 - emotional_resilience DECIMAL(5, 2) DEFAULT 50.00, -- 情绪韧性 - action_power DECIMAL(5, 2) DEFAULT 50.00, -- 行动力 - empathy DECIMAL(5, 2) DEFAULT 50.00, -- 共情力 - life_enthusiasm DECIMAL(5, 2) DEFAULT 50.00, -- 生活热度 + self_awareness DECIMAL(5, 2) DEFAULT 50.00 COMMENT '自我感知', -- 自我感知 + emotional_resilience DECIMAL(5, 2) DEFAULT 50.00 COMMENT '情绪韧性', -- 情绪韧性 + action_power DECIMAL(5, 2) DEFAULT 50.00 COMMENT '行动力', -- 行动力 + empathy DECIMAL(5, 2) DEFAULT 50.00 COMMENT '共情力', -- 共情力 + life_enthusiasm DECIMAL(5, 2) DEFAULT 50.00 COMMENT '生活热度', -- 生活热度 -- 状态字段 - status TINYINT DEFAULT 1, -- 状态: 0-禁用, 1-正常 - is_verified TINYINT DEFAULT 0, -- 是否已验证: 0-未验证, 1-已验证 - last_active_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 最后活跃时间 + status TINYINT DEFAULT 1 COMMENT '状态: 0-禁用, 1-正常', -- 状态: 0-禁用, 1-正常 + is_verified TINYINT DEFAULT 0 COMMENT '是否已验证: 0-未验证, 1-已验证', -- 是否已验证: 0-未验证, 1-已验证 + last_active_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '最后活跃时间', -- 最后活跃时间 -- 第三方登录字段 - third_party_id VARCHAR(128), -- 第三方平台ID - third_party_type VARCHAR(32), -- 第三方平台类型: wechat, qq, wechat-mp + third_party_id VARCHAR(128) COMMENT '第三方平台ID', -- 第三方平台ID + third_party_type VARCHAR(32) COMMENT '第三方平台类型: wechat, qq, wechat-mp', -- 第三方平台类型: wechat, qq, wechat-mp -- 公共字段 - create_by VARCHAR(36), -- 创建人ID - create_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间 - update_by VARCHAR(36), -- 更新人ID - update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 更新时间 - is_deleted TINYINT DEFAULT 0, -- 是否删除: 0-未删除, 1-已删除 - remarks VARCHAR(500) -- 备注 + create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 + update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 + is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 + remarks VARCHAR(500) COMMENT '备注' -- 备注 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表'; -- ============================================================================ @@ -114,41 +114,41 @@ CREATE TABLE user ( -- 关联说明: user_id 关联 user.id,通过代码逻辑维护关联关系 -- ============================================================================ CREATE TABLE conversation ( - id VARCHAR(36) PRIMARY KEY, -- UUID主键 - user_id VARCHAR(36) , -- 用户ID (关联user.id) - user_type VARCHAR(20) DEFAULT 'registered', -- 用户类型: registered-注册用户, guest-访客用户 - title VARCHAR(200), -- 对话标题 - type VARCHAR(50) DEFAULT 'emotion_chat', -- 对话类型 - status VARCHAR(20) DEFAULT 'active', -- 状态: active-活跃, ended-结束, archived-归档 - coze_conversation_id VARCHAR(100), -- Coze对话ID - bot_id VARCHAR(50), -- 使用的Bot ID - workflow_id VARCHAR(50), -- 使用的Workflow ID - initial_message TEXT, -- 初始消息 - context TEXT, -- 上下文信息 - primary_emotion VARCHAR(50), -- 主要情绪 - emotion_intensity DECIMAL(3, 2), -- 情绪强度 - emotion_trend VARCHAR(50), -- 情绪趋势 - keywords JSON, -- 关键词 - ai_insights TEXT, -- AI洞察 - confidence DECIMAL(3, 2), -- 分析置信度 - start_time DATETIME, -- 开始时间 - end_time DATETIME, -- 结束时间 - last_active_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 最后活跃时间 - message_count INT DEFAULT 0, -- 消息数量 - total_tokens INT DEFAULT 0, -- 总Token使用量 - total_cost DECIMAL(10, 4) DEFAULT 0.0000, -- 总费用 - client_ip VARCHAR(45), -- 客户端IP地址 (支持IPv6) - user_agent TEXT, -- 用户代理信息 - summary TEXT, -- 对话摘要 - tags JSON, -- 标签 - metadata JSON, -- 扩展元数据 + id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + user_id VARCHAR(36) COMMENT '用户ID (关联user.id)', -- 用户ID (关联user.id) + user_type VARCHAR(20) DEFAULT 'registered' COMMENT '用户类型: registered-注册用户, guest-访客用户', -- 用户类型: registered-注册用户, guest-访客用户 + title VARCHAR(200) COMMENT '对话标题', -- 对话标题 + type VARCHAR(50) DEFAULT 'emotion_chat' COMMENT '对话类型', -- 对话类型 + status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active-活跃, ended-结束, archived-归档', -- 状态: active-活跃, ended-结束, archived-归档 + coze_conversation_id VARCHAR(100) COMMENT 'Coze对话ID', -- Coze对话ID + bot_id VARCHAR(50) COMMENT '使用的Bot ID', -- 使用的Bot ID + workflow_id VARCHAR(50) COMMENT '使用的Workflow ID', -- 使用的Workflow ID + initial_message TEXT COMMENT '初始消息', -- 初始消息 + context TEXT COMMENT '上下文信息', -- 上下文信息 + primary_emotion VARCHAR(50) COMMENT '主要情绪', -- 主要情绪 + emotion_intensity DECIMAL(3, 2) COMMENT '情绪强度', -- 情绪强度 + emotion_trend VARCHAR(50) COMMENT '情绪趋势', -- 情绪趋势 + keywords JSON COMMENT '关键词', -- 关键词 + ai_insights TEXT COMMENT 'AI洞察', -- AI洞察 + confidence DECIMAL(3, 2) COMMENT '分析置信度', -- 分析置信度 + start_time DATETIME COMMENT '开始时间', -- 开始时间 + end_time DATETIME COMMENT '结束时间', -- 结束时间 + last_active_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '最后活跃时间', -- 最后活跃时间 + message_count INT DEFAULT 0 COMMENT '消息数量', -- 消息数量 + total_tokens INT DEFAULT 0 COMMENT '总Token使用量', -- 总Token使用量 + total_cost DECIMAL(10, 4) DEFAULT 0.0000 COMMENT '总费用', -- 总费用 + client_ip VARCHAR(45) COMMENT '客户端IP地址 (支持IPv6)', -- 客户端IP地址 (支持IPv6) + user_agent TEXT COMMENT '用户代理信息', -- 用户代理信息 + summary TEXT COMMENT '对话摘要', -- 对话摘要 + tags JSON COMMENT '标签', -- 标签 + metadata JSON COMMENT '扩展元数据', -- 扩展元数据 -- 公共字段 - create_by VARCHAR(36), -- 创建人ID - create_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间 - update_by VARCHAR(36), -- 更新人ID - update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 更新时间 - is_deleted TINYINT DEFAULT 0, -- 是否删除: 0-未删除, 1-已删除 - remarks VARCHAR(500) -- 备注 + create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 + update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 + is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 + remarks VARCHAR(500) COMMENT '备注' -- 备注 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '对话表'; -- ============================================================================ @@ -156,368 +156,372 @@ CREATE TABLE conversation ( -- 关联说明: conversation_id 关联 conversation.id,通过代码逻辑维护关联关系 -- ============================================================================ CREATE TABLE message ( - id VARCHAR(36) PRIMARY KEY, -- UUID主键 - conversation_id VARCHAR(36) , -- 对话ID (关联conversation.id) - content TEXT , -- 消息内容 - type VARCHAR(50) DEFAULT 'text', -- 消息类型 - sender VARCHAR(20) , -- 发送者: user-用户, assistant-AI助手 - timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, -- 消息时间戳 - coze_chat_id VARCHAR(50), -- Coze平台的聊天ID - coze_message_id VARCHAR(50), -- Coze平台的消息ID - status VARCHAR(20) DEFAULT 'sent', -- 消息状态: sending/sent/failed/processing - error_message TEXT, -- 错误信息 - emotion_score DECIMAL(3, 2), -- 情绪评分 - emotion_type VARCHAR(50), -- 情绪类型 - emotion_confidence DECIMAL(3, 2), -- 情绪分析置信度 - prompt_tokens INT DEFAULT 0, -- 输入Token数 - completion_tokens INT DEFAULT 0, -- 输出Token数 - total_tokens INT DEFAULT 0, -- 总Token数 - api_cost DECIMAL(10, 6) DEFAULT 0.000000, -- API调用费用 - is_read TINYINT DEFAULT 0, -- 是否已读: 0-未读, 1-已读 - parent_message_id VARCHAR(36), -- 父消息ID(用于回复链) - emotion_analysis JSON, -- 情绪分析结果 - metadata JSON, -- 扩展元数据 + id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + conversation_id VARCHAR(36) COMMENT '对话ID (关联conversation.id)', -- 对话ID (关联conversation.id) + content TEXT COMMENT '消息内容', -- 消息内容 + type VARCHAR(50) DEFAULT 'text' COMMENT '消息类型', -- 消息类型 + sender VARCHAR(20) COMMENT '发送者: user-用户, assistant-AI助手', -- 发送者: user-用户, assistant-AI助手 + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '消息时间戳', -- 消息时间戳 + coze_chat_id VARCHAR(50) COMMENT 'Coze平台的聊天ID', -- Coze平台的聊天ID + coze_message_id VARCHAR(50) COMMENT 'Coze平台的消息ID', -- Coze平台的消息ID + status VARCHAR(20) DEFAULT 'sent' COMMENT '消息状态: sending/sent/failed/processing', -- 消息状态: sending/sent/failed/processing + error_message TEXT COMMENT '错误信息', -- 错误信息 + emotion_score DECIMAL(3, 2) COMMENT '情绪评分', -- 情绪评分 + emotion_type VARCHAR(50) COMMENT '情绪类型', -- 情绪类型 + emotion_confidence DECIMAL(3, 2) COMMENT '情绪分析置信度', -- 情绪分析置信度 + prompt_tokens INT DEFAULT 0 COMMENT '输入Token数', -- 输入Token数 + completion_tokens INT DEFAULT 0 COMMENT '输出Token数', -- 输出Token数 + total_tokens INT DEFAULT 0 COMMENT '总Token数', -- 总Token数 + api_cost DECIMAL(10, 6) DEFAULT 0.000000 COMMENT 'API调用费用', -- API调用费用 + is_read TINYINT DEFAULT 0 COMMENT '是否已读: 0-未读, 1-已读', -- 是否已读: 0-未读, 1-已读 + parent_message_id VARCHAR(36) COMMENT '父消息ID(用于回复链)', -- 父消息ID(用于回复链) + emotion_analysis JSON COMMENT '情绪分析结果', -- 情绪分析结果 + metadata JSON COMMENT '扩展元数据', -- 扩展元数据 + user_id VARCHAR(36) COMMENT '用户ID (注册用户或访客用户)', -- 用户ID (注册用户或访客用户) + user_type VARCHAR(20) COMMENT '用户类型 (registered/guest)', -- 用户类型 (registered/guest) + coze_role VARCHAR(20) COMMENT 'Coze消息角色 (user/assistant/system)', -- Coze消息角色 (user/assistant/system) + coze_content_type VARCHAR(50) COMMENT 'Coze消息内容类型 (text/image/file等)', -- Coze消息内容类型 (text/image/file等) -- 公共字段 - create_by VARCHAR(36), -- 创建人ID - create_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间 - update_by VARCHAR(36), -- 更新人ID - update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 更新时间 - is_deleted TINYINT DEFAULT 0, -- 是否删除: 0-未删除, 1-已删除 - remarks VARCHAR(500) -- 备注 + create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 + update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 + is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 + remarks VARCHAR(500) COMMENT '备注' -- 备注 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '消息表'; -- ============================================================================ -- 4. Coze API调用记录表 (coze_api_call) - 优化版本 -- ============================================================================ CREATE TABLE coze_api_call ( - id VARCHAR(36) PRIMARY KEY, -- UUID主键 - conversation_id VARCHAR(36), -- 对话ID - message_id VARCHAR(36), -- 消息ID + id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + conversation_id VARCHAR(36) COMMENT '对话ID', -- 对话ID + message_id VARCHAR(36) COMMENT '消息ID', -- 消息ID -- Coze API 信息 - coze_chat_id VARCHAR(50), -- Coze聊天ID - coze_conversation_id VARCHAR(50), -- Coze对话ID - bot_id VARCHAR(50) , -- Bot ID - workflow_id VARCHAR(50), -- Workflow ID - user_id VARCHAR(36) , -- 用户ID + coze_chat_id VARCHAR(50) COMMENT 'Coze聊天ID', -- Coze聊天ID + coze_conversation_id VARCHAR(50) COMMENT 'Coze对话ID', -- Coze对话ID + bot_id VARCHAR(50) COMMENT 'Bot ID', -- Bot ID + workflow_id VARCHAR(50) COMMENT 'Workflow ID', -- Workflow ID + user_id VARCHAR(36) COMMENT '用户ID', -- 用户ID -- 请求信息 - request_type VARCHAR(20) , -- 请求类型: chat/stream/retrieve/messages - request_url VARCHAR(500), -- 请求URL - request_body JSON, -- 请求体 - request_headers JSON, -- 请求头 + request_type VARCHAR(20) COMMENT '请求类型: chat/stream/retrieve/messages', -- 请求类型: chat/stream/retrieve/messages + request_url VARCHAR(500) COMMENT '请求URL', -- 请求URL + request_body JSON COMMENT '请求体', -- 请求体 + request_headers JSON COMMENT '请求头', -- 请求头 -- 用户消息内容 - user_message TEXT, -- 用户输入的消息内容 - user_message_type VARCHAR(20) DEFAULT 'text', -- 用户消息类型: text/image/file + user_message TEXT COMMENT '用户输入的消息内容', -- 用户输入的消息内容 + user_message_type VARCHAR(20) DEFAULT 'text' COMMENT '用户消息类型: text/image/file', -- 用户消息类型: text/image/file -- AI回复内容 - ai_reply TEXT, -- AI回复的消息内容 - ai_reply_type VARCHAR(20) DEFAULT 'text', -- AI回复类型: text/image/file + ai_reply TEXT COMMENT 'AI回复的消息内容', -- AI回复的消息内容 + ai_reply_type VARCHAR(20) DEFAULT 'text' COMMENT 'AI回复类型: text/image/file', -- AI回复类型: text/image/file -- 响应信息 - response_status INT, -- HTTP状态码 - response_body JSON, -- 响应体 - response_headers JSON, -- 响应头 + response_status INT COMMENT 'HTTP状态码', -- HTTP状态码 + response_body JSON COMMENT '响应体', -- 响应体 + response_headers JSON COMMENT '响应头', -- 响应头 -- 轮询信息 - poll_count INT DEFAULT 0, -- 轮询次数 - poll_start_time DATETIME, -- 轮询开始时间 - poll_end_time DATETIME, -- 轮询结束时间 - final_status VARCHAR(20), -- 最终状态: completed/failed/timeout + poll_count INT DEFAULT 0 COMMENT '轮询次数', -- 轮询次数 + poll_start_time DATETIME COMMENT '轮询开始时间', -- 轮询开始时间 + poll_end_time DATETIME COMMENT '轮询结束时间', -- 轮询结束时间 + final_status VARCHAR(20) COMMENT '最终状态: completed/failed/timeout', -- 最终状态: completed/failed/timeout -- 状态和时间 - status VARCHAR(20) , -- 调用状态: pending/success/failed/timeout - start_time DATETIME , -- 开始时间 - end_time DATETIME, -- 结束时间 - duration_ms INT, -- 耗时(毫秒) + status VARCHAR(20) COMMENT '调用状态: pending/success/failed/timeout', -- 调用状态: pending/success/failed/timeout + start_time DATETIME COMMENT '开始时间', -- 开始时间 + end_time DATETIME COMMENT '结束时间', -- 结束时间 + duration_ms INT COMMENT '耗时(毫秒)', -- 耗时(毫秒) -- 使用统计 - prompt_tokens INT DEFAULT 0, -- 输入Token数 - completion_tokens INT DEFAULT 0, -- 输出Token数 - total_tokens INT DEFAULT 0, -- 总Token数 - cost DECIMAL(10, 6) DEFAULT 0.000000, -- 费用 + prompt_tokens INT DEFAULT 0 COMMENT '输入Token数', -- 输入Token数 + completion_tokens INT DEFAULT 0 COMMENT '输出Token数', -- 输出Token数 + total_tokens INT DEFAULT 0 COMMENT '总Token数', -- 总Token数 + cost DECIMAL(10, 6) DEFAULT 0.000000 COMMENT '费用', -- 费用 -- 功能调用信息 - function_calls JSON, -- 函数调用记录 - function_results JSON, -- 函数调用结果 + function_calls JSON COMMENT '函数调用记录', -- 函数调用记录 + function_results JSON COMMENT '函数调用结果', -- 函数调用结果 -- 错误信息 - error_code VARCHAR(50), -- 错误代码 - error_message TEXT, -- 错误信息 + error_code VARCHAR(50) COMMENT '错误代码', -- 错误代码 + error_message TEXT COMMENT '错误信息', -- 错误信息 -- 扩展信息 - client_ip VARCHAR(45), -- 客户端IP - user_agent TEXT, -- 用户代理 - session_id VARCHAR(100), -- 会话ID - trace_id VARCHAR(100), -- 追踪ID - metadata JSON, -- 扩展元数据 + client_ip VARCHAR(45) COMMENT '客户端IP', -- 客户端IP + user_agent TEXT COMMENT '用户代理', -- 用户代理 + session_id VARCHAR(100) COMMENT '会话ID', -- 会话ID + trace_id VARCHAR(100) COMMENT '追踪ID', -- 追踪ID + metadata JSON COMMENT '扩展元数据', -- 扩展元数据 -- 公共字段 - create_by VARCHAR(36), -- 创建人ID - create_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间 - update_by VARCHAR(36), -- 更新人ID - update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 更新时间 - is_deleted TINYINT DEFAULT 0, -- 是否删除: 0-未删除, 1-已删除 - remarks VARCHAR(500) -- 备注 + create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 + update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 + is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 + remarks VARCHAR(500) COMMENT '备注' -- 备注 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Coze API调用记录表 - 完整版本'; -- ============================================================================ -- 5. 情绪分析表 (emotion_analysis) -- ============================================================================ CREATE TABLE emotion_analysis ( - id VARCHAR(36) PRIMARY KEY, -- UUID主键 - user_id VARCHAR(36) , -- 用户ID - message_id VARCHAR(36), -- 关联消息ID - text TEXT , -- 分析文本 - primary_emotion VARCHAR(50), -- 主要情绪 - intensity DECIMAL(3, 2), -- 情绪强度 - polarity VARCHAR(20), -- 情绪极性: positive-积极, negative-消极, neutral-中性 - confidence DECIMAL(3, 2), -- 置信度 - emotions JSON, -- 情绪分布详情 - keywords JSON, -- 关键词列表 - suggestion TEXT, -- 建议 - analysis_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 分析时间 - metadata JSON, -- 扩展元数据 + id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + user_id VARCHAR(36) COMMENT '用户ID', -- 用户ID + message_id VARCHAR(36) COMMENT '关联消息ID', -- 关联消息ID + text TEXT COMMENT '分析文本', -- 分析文本 + primary_emotion VARCHAR(50) COMMENT '主要情绪', -- 主要情绪 + intensity DECIMAL(3, 2) COMMENT '情绪强度', -- 情绪强度 + polarity VARCHAR(20) COMMENT '情绪极性: positive-积极, negative-消极, neutral-中性', -- 情绪极性: positive-积极, negative-消极, neutral-中性 + confidence DECIMAL(3, 2) COMMENT '置信度', -- 置信度 + emotions JSON COMMENT '情绪分布详情', -- 情绪分布详情 + keywords JSON COMMENT '关键词列表', -- 关键词列表 + suggestion TEXT COMMENT '建议', -- 建议 + analysis_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '分析时间', -- 分析时间 + metadata JSON COMMENT '扩展元数据', -- 扩展元数据 -- 公共字段 - create_by VARCHAR(36), -- 创建人ID - create_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间 - update_by VARCHAR(36), -- 更新人ID - update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 更新时间 - is_deleted TINYINT DEFAULT 0, -- 是否删除: 0-未删除, 1-已删除 - remarks VARCHAR(500) -- 备注 + create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 + update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 + is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 + remarks VARCHAR(500) COMMENT '备注' -- 备注 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '情绪分析表'; -- ============================================================================ -- 6. 情绪记录表 (emotion_record) -- ============================================================================ CREATE TABLE emotion_record ( - id VARCHAR(36) PRIMARY KEY, -- UUID主键 - user_id VARCHAR(36) , -- 用户ID - record_date DATE , -- 记录日期 - emotion_type VARCHAR(50) , -- 情绪类型 - intensity DECIMAL(3, 2) , -- 情绪强度 - triggers TEXT, -- 触发因素 - description TEXT, -- 描述 - tags JSON, -- 标签 - weather VARCHAR(50), -- 天气 - location VARCHAR(100), -- 地点 - activity VARCHAR(100), -- 活动 - people VARCHAR(200), -- 相关人物 - notes TEXT, -- 备注 + id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + user_id VARCHAR(36) COMMENT '用户ID', -- 用户ID + record_date DATE COMMENT '记录日期', -- 记录日期 + emotion_type VARCHAR(50) COMMENT '情绪类型', -- 情绪类型 + intensity DECIMAL(3, 2) COMMENT '情绪强度', -- 情绪强度 + triggers TEXT COMMENT '触发因素', -- 触发因素 + description TEXT COMMENT '描述', -- 描述 + tags JSON COMMENT '标签', -- 标签 + weather VARCHAR(50) COMMENT '天气', -- 天气 + location VARCHAR(100) COMMENT '地点', -- 地点 + activity VARCHAR(100) COMMENT '活动', -- 活动 + people VARCHAR(200) COMMENT '相关人物', -- 相关人物 + notes TEXT COMMENT '备注', -- 备注 -- 公共字段 - create_by VARCHAR(36), -- 创建人ID - create_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间 - update_by VARCHAR(36), -- 更新人ID - update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 更新时间 - is_deleted TINYINT DEFAULT 0, -- 是否删除: 0-未删除, 1-已删除 - remarks VARCHAR(500) -- 备注 + create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 + update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 + is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 + remarks VARCHAR(500) COMMENT '备注' -- 备注 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '情绪记录表'; -- ============================================================================ -- 7. 成长课题表 (growth_topic) -- ============================================================================ CREATE TABLE growth_topic ( - id VARCHAR(36) PRIMARY KEY, -- UUID主键 - title VARCHAR(100) , -- 课题标题 - category VARCHAR(50) , -- 分类 - difficulty VARCHAR(20) , -- 难度: easy-简单, medium-中等, hard-困难 - description TEXT, -- 描述 - content TEXT, -- 内容 - duration_days INT, -- 持续天数 - unlock_conditions JSON, -- 解锁条件 - is_unlocked TINYINT DEFAULT 1, -- 是否解锁 - progress DECIMAL(5, 2) DEFAULT 0.00, -- 进度百分比 - completed_time DATETIME, -- 完成时间 - rewards JSON, -- 奖励 + id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + title VARCHAR(100) COMMENT '课题标题', -- 课题标题 + category VARCHAR(50) COMMENT '分类', -- 分类 + difficulty VARCHAR(20) COMMENT '难度: easy-简单, medium-中等, hard-困难', -- 难度: easy-简单, medium-中等, hard-困难 + description TEXT COMMENT '描述', -- 描述 + content TEXT COMMENT '内容', -- 内容 + duration_days INT COMMENT '持续天数', -- 持续天数 + unlock_conditions JSON COMMENT '解锁条件', -- 解锁条件 + is_unlocked TINYINT DEFAULT 1 COMMENT '是否解锁', -- 是否解锁 + progress DECIMAL(5, 2) DEFAULT 0.00 COMMENT '进度百分比', -- 进度百分比 + completed_time DATETIME COMMENT '完成时间', -- 完成时间 + rewards JSON COMMENT '奖励', -- 奖励 -- 公共字段 - create_by VARCHAR(36), -- 创建人ID - create_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间 - update_by VARCHAR(36), -- 更新人ID - update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 更新时间 - is_deleted TINYINT DEFAULT 0, -- 是否删除: 0-未删除, 1-已删除 - remarks VARCHAR(500) -- 备注 + create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 + update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 + is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 + remarks VARCHAR(500) COMMENT '备注' -- 备注 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '成长课题表'; -- ============================================================================ -- 8. 课题互动表 (topic_interaction) -- ============================================================================ CREATE TABLE topic_interaction ( - id VARCHAR(36) PRIMARY KEY, -- UUID主键 - topic_id VARCHAR(36) , -- 课题ID - type VARCHAR(50) , -- 互动类型 - content TEXT, -- 内容 - user_input TEXT, -- 用户输入 - ai_response TEXT, -- AI回应 - rating INT, -- 评分 - feedback TEXT, -- 反馈 - completed_time DATETIME, -- 完成时间 + id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + topic_id VARCHAR(36) COMMENT '课题ID', -- 课题ID + type VARCHAR(50) COMMENT '互动类型', -- 互动类型 + content TEXT COMMENT '内容', -- 内容 + user_input TEXT COMMENT '用户输入', -- 用户输入 + ai_response TEXT COMMENT 'AI回应', -- AI回应 + rating INT COMMENT '评分', -- 评分 + feedback TEXT COMMENT '反馈', -- 反馈 + completed_time DATETIME COMMENT '完成时间', -- 完成时间 -- 公共字段 - create_by VARCHAR(36), -- 创建人ID - create_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间 - update_by VARCHAR(36), -- 更新人ID - update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 更新时间 - is_deleted TINYINT DEFAULT 0, -- 是否删除: 0-未删除, 1-已删除 - remarks VARCHAR(500) -- 备注 + create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 + update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 + is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 + remarks VARCHAR(500) COMMENT '备注' -- 备注 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '课题互动表'; -- ============================================================================ -- 9. 地点标记表 (location_pin) -- ============================================================================ CREATE TABLE location_pin ( - id VARCHAR(36) PRIMARY KEY, -- UUID主键 - name VARCHAR(100) , -- 地点名称 - type VARCHAR(50) , -- 地点类型 - category VARCHAR(50), -- 地点分类 - latitude DECIMAL(10, 8) , -- 纬度 - longitude DECIMAL(11, 8) , -- 经度 - address VARCHAR(200), -- 地址 - description TEXT, -- 描述 - created_by VARCHAR(36), -- 创建者 - likes INT DEFAULT 0, -- 点赞数 - visits INT DEFAULT 0, -- 访问数 - is_bookmarked TINYINT DEFAULT 0, -- 是否收藏 - last_visit_time DATETIME, -- 最后访问时间 + id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + name VARCHAR(100) COMMENT '地点名称', -- 地点名称 + type VARCHAR(50) COMMENT '地点类型', -- 地点类型 + category VARCHAR(50) COMMENT '地点分类', -- 地点分类 + latitude DECIMAL(10, 8) COMMENT '纬度', -- 纬度 + longitude DECIMAL(11, 8) COMMENT '经度', -- 经度 + address VARCHAR(200) COMMENT '地址', -- 地址 + description TEXT COMMENT '描述', -- 描述 + created_by VARCHAR(36) COMMENT '创建者', -- 创建者 + likes INT DEFAULT 0 COMMENT '点赞数', -- 点赞数 + visits INT DEFAULT 0 COMMENT '访问数', -- 访问数 + is_bookmarked TINYINT DEFAULT 0 COMMENT '是否收藏', -- 是否收藏 + last_visit_time DATETIME COMMENT '最后访问时间', -- 最后访问时间 -- 公共字段 - create_by VARCHAR(36), -- 创建人ID - create_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间 - update_by VARCHAR(36), -- 更新人ID - update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 更新时间 - is_deleted TINYINT DEFAULT 0, -- 是否删除: 0-未删除, 1-已删除 - remarks VARCHAR(500) -- 备注 + create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 + update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 + is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 + remarks VARCHAR(500) COMMENT '备注' -- 备注 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '地点标记表'; -- ============================================================================ -- 10. 社区帖子表 (community_post) -- ============================================================================ CREATE TABLE community_post ( - id VARCHAR(36) PRIMARY KEY, -- UUID主键 - user_id VARCHAR(36) , -- 用户ID - location_id VARCHAR(36), -- 地点ID - title VARCHAR(200), -- 标题 - content TEXT , -- 内容 - type VARCHAR(50) , -- 帖子类型 - images JSON, -- 图片列表 - tags JSON, -- 标签 - likes INT DEFAULT 0, -- 点赞数 - view_count INT DEFAULT 0, -- 浏览数 - comment_count INT DEFAULT 0, -- 评论数 - is_private TINYINT DEFAULT 0, -- 是否私密 + id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + user_id VARCHAR(36) COMMENT '用户ID', -- 用户ID + location_id VARCHAR(36) COMMENT '地点ID', -- 地点ID + title VARCHAR(200) COMMENT '标题', -- 标题 + content TEXT COMMENT '内容', -- 内容 + type VARCHAR(50) COMMENT '帖子类型', -- 帖子类型 + images JSON COMMENT '图片列表', -- 图片列表 + tags JSON COMMENT '标签', -- 标签 + likes INT DEFAULT 0 COMMENT '点赞数', -- 点赞数 + view_count INT DEFAULT 0 COMMENT '浏览数', -- 浏览数 + comment_count INT DEFAULT 0 COMMENT '评论数', -- 评论数 + is_private TINYINT DEFAULT 0 COMMENT '是否私密', -- 是否私密 -- 公共字段 - create_by VARCHAR(36), -- 创建人ID - create_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间 - update_by VARCHAR(36), -- 更新人ID - update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 更新时间 - is_deleted TINYINT DEFAULT 0, -- 是否删除: 0-未删除, 1-已删除 - remarks VARCHAR(500) -- 备注 + create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 + update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 + is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 + remarks VARCHAR(500) COMMENT '备注' -- 备注 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社区帖子表'; -- ============================================================================ -- 11. 评论表 (comment) -- ============================================================================ CREATE TABLE comment ( - id VARCHAR(36) PRIMARY KEY, -- UUID主键 - post_id VARCHAR(36) , -- 帖子ID - user_id VARCHAR(36) , -- 用户ID - content TEXT , -- 评论内容 - reply_to_id VARCHAR(36), -- 回复的评论ID - likes INT DEFAULT 0, -- 点赞数 + id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + post_id VARCHAR(36) COMMENT '帖子ID', -- 帖子ID + user_id VARCHAR(36) COMMENT '用户ID', -- 用户ID + content TEXT COMMENT '评论内容', -- 评论内容 + reply_to_id VARCHAR(36) COMMENT '回复的评论ID', -- 回复的评论ID + likes INT DEFAULT 0 COMMENT '点赞数', -- 点赞数 -- 公共字段 - create_by VARCHAR(36), -- 创建人ID - create_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间 - update_by VARCHAR(36), -- 更新人ID - update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 更新时间 - is_deleted TINYINT DEFAULT 0, -- 是否删除: 0-未删除, 1-已删除 - remarks VARCHAR(500) -- 备注 + create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 + update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 + is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 + remarks VARCHAR(500) COMMENT '备注' -- 备注 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '评论表'; -- ============================================================================ -- 12. 成就表 (achievement) -- ============================================================================ CREATE TABLE achievement ( - id VARCHAR(36) PRIMARY KEY, -- UUID主键 - title VARCHAR(100) , -- 成就标题 - description TEXT, -- 描述 - category VARCHAR(50) , -- 分类 - icon VARCHAR(200), -- 图标 - rarity VARCHAR(20) , -- 稀有度 - condition_type VARCHAR(50), -- 条件类型 - condition_value JSON, -- 条件值 - rewards JSON, -- 奖励 - unlocked_time DATETIME, -- 解锁时间 - progress DECIMAL(5, 2) DEFAULT 0.00, -- 进度 - is_hidden TINYINT DEFAULT 0, -- 是否隐藏 + id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + title VARCHAR(100) COMMENT '成就标题', -- 成就标题 + description TEXT COMMENT '描述', -- 描述 + category VARCHAR(50) COMMENT '分类', -- 分类 + icon VARCHAR(200) COMMENT '图标', -- 图标 + rarity VARCHAR(20) COMMENT '稀有度', -- 稀有度 + condition_type VARCHAR(50) COMMENT '条件类型', -- 条件类型 + condition_value JSON COMMENT '条件值', -- 条件值 + rewards JSON COMMENT '奖励', -- 奖励 + unlocked_time DATETIME COMMENT '解锁时间', -- 解锁时间 + progress DECIMAL(5, 2) DEFAULT 0.00 COMMENT '进度', -- 进度 + is_hidden TINYINT DEFAULT 0 COMMENT '是否隐藏', -- 是否隐藏 -- 公共字段 - create_by VARCHAR(36), -- 创建人ID - create_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间 - update_by VARCHAR(36), -- 更新人ID - update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 更新时间 - is_deleted TINYINT DEFAULT 0, -- 是否删除: 0-未删除, 1-已删除 - remarks VARCHAR(500) -- 备注 + create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 + update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 + is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 + remarks VARCHAR(500) COMMENT '备注' -- 备注 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '成就表'; -- ============================================================================ -- 13. 奖励表 (reward) -- ============================================================================ CREATE TABLE reward ( - id VARCHAR(36) PRIMARY KEY, -- UUID主键 - topic_id VARCHAR(36), -- 课题ID - achievement_id VARCHAR(36), -- 成就ID - type VARCHAR(50) , -- 奖励类型 - name VARCHAR(100) , -- 奖励名称 - description TEXT, -- 描述 - icon VARCHAR(200), -- 图标 - rarity VARCHAR(20), -- 稀有度 - value JSON, -- 奖励值 - earned_time DATETIME, -- 获得时间 - is_new TINYINT DEFAULT 1, -- 是否新获得 + id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + topic_id VARCHAR(36) COMMENT '课题ID', -- 课题ID + achievement_id VARCHAR(36) COMMENT '成就ID', -- 成就ID + type VARCHAR(50) COMMENT '奖励类型', -- 奖励类型 + name VARCHAR(100) COMMENT '奖励名称', -- 奖励名称 + description TEXT COMMENT '描述', -- 描述 + icon VARCHAR(200) COMMENT '图标', -- 图标 + rarity VARCHAR(20) COMMENT '稀有度', -- 稀有度 + value JSON COMMENT '奖励值', -- 奖励值 + earned_time DATETIME COMMENT '获得时间', -- 获得时间 + is_new TINYINT DEFAULT 1 COMMENT '是否新获得', -- 是否新获得 -- 公共字段 - create_by VARCHAR(36), -- 创建人ID - create_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间 - update_by VARCHAR(36), -- 更新人ID - update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 更新时间 - is_deleted TINYINT DEFAULT 0, -- 是否删除: 0-未删除, 1-已删除 - remarks VARCHAR(500) -- 备注 + create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 + update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 + is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 + remarks VARCHAR(500) COMMENT '备注' -- 备注 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '奖励表'; -- ============================================================================ -- 14. 访客用户表 (guest_user) -- ============================================================================ CREATE TABLE guest_user ( - id VARCHAR(36) PRIMARY KEY, -- UUID主键 - guest_user_id VARCHAR(50) UNIQUE, -- 访客用户ID (格式: guest_xxx) - ip_address VARCHAR(45) , -- 客户端IP地址 (支持IPv6) - user_agent TEXT, -- 用户代理信息 - nickname VARCHAR(50), -- 访客昵称 - avatar VARCHAR(500), -- 访客头像 - last_active_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 最后活跃时间 - conversation_count INT DEFAULT 0, -- 会话数量 - message_count INT DEFAULT 0, -- 消息数量 - location VARCHAR(100), -- IP地址的地理位置信息 - device_info VARCHAR(200), -- 设备信息 + id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + guest_user_id VARCHAR(50) UNIQUE COMMENT '访客用户ID (格式: guest_xxx)', -- 访客用户ID (格式: guest_xxx) + ip_address VARCHAR(45) COMMENT '客户端IP地址 (支持IPv6)', -- 客户端IP地址 (支持IPv6) + user_agent TEXT COMMENT '用户代理信息', -- 用户代理信息 + nickname VARCHAR(50) COMMENT '访客昵称', -- 访客昵称 + avatar VARCHAR(500) COMMENT '访客头像', -- 访客头像 + last_active_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '最后活跃时间', -- 最后活跃时间 + conversation_count INT DEFAULT 0 COMMENT '会话数量', -- 会话数量 + message_count INT DEFAULT 0 COMMENT '消息数量', -- 消息数量 + location VARCHAR(100) COMMENT 'IP地址的地理位置信息', -- IP地址的地理位置信息 + device_info VARCHAR(200) COMMENT '设备信息', -- 设备信息 -- 公共字段 - create_by VARCHAR(36), -- 创建人ID - create_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间 - update_by VARCHAR(36), -- 更新人ID - update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 更新时间 - is_deleted TINYINT DEFAULT 0, -- 是否删除: 0-未删除, 1-已删除 - remarks VARCHAR(500) -- 备注 + create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 + update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 + is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 + remarks VARCHAR(500) COMMENT '备注' -- 备注 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '访客用户表'; -- ============================================================================ -- 15. 用户统计表 (user_stats) -- ============================================================================ CREATE TABLE user_stats ( - id VARCHAR(36) PRIMARY KEY, -- UUID主键 - user_id VARCHAR(36) UNIQUE, -- 用户ID - total_conversations INT DEFAULT 0, -- 总对话数 - total_messages INT DEFAULT 0, -- 总消息数 - total_emotions_recorded INT DEFAULT 0, -- 总情绪记录数 - topics_completed INT DEFAULT 0, -- 完成的课题数 - achievements_unlocked INT DEFAULT 0, -- 解锁的成就数 - total_points INT DEFAULT 0, -- 总积分 - consecutive_days INT DEFAULT 0, -- 连续使用天数 - max_consecutive_days INT DEFAULT 0, -- 最大连续天数 - locations_visited INT DEFAULT 0, -- 访问的地点数 - posts_created INT DEFAULT 0, -- 创建的帖子数 - comments_made INT DEFAULT 0, -- 评论数 - likes_received INT DEFAULT 0, -- 收到的点赞数 - social_interactions INT DEFAULT 0, -- 社交互动数 + id VARCHAR(36) PRIMARY KEY COMMENT 'UUID主键', -- UUID主键 + user_id VARCHAR(36) UNIQUE COMMENT '用户ID', -- 用户ID + total_conversations INT DEFAULT 0 COMMENT '总对话数', -- 总对话数 + total_messages INT DEFAULT 0 COMMENT '总消息数', -- 总消息数 + total_emotions_recorded INT DEFAULT 0 COMMENT '总情绪记录数', -- 总情绪记录数 + topics_completed INT DEFAULT 0 COMMENT '完成的课题数', -- 完成的课题数 + achievements_unlocked INT DEFAULT 0 COMMENT '解锁的成就数', -- 解锁的成就数 + total_points INT DEFAULT 0 COMMENT '总积分', -- 总积分 + consecutive_days INT DEFAULT 0 COMMENT '连续使用天数', -- 连续使用天数 + max_consecutive_days INT DEFAULT 0 COMMENT '最大连续天数', -- 最大连续天数 + locations_visited INT DEFAULT 0 COMMENT '访问的地点数', -- 访问的地点数 + posts_created INT DEFAULT 0 COMMENT '创建的帖子数', -- 创建的帖子数 + comments_made INT DEFAULT 0 COMMENT '评论数', -- 评论数 + likes_received INT DEFAULT 0 COMMENT '收到的点赞数', -- 收到的点赞数 + social_interactions INT DEFAULT 0 COMMENT '社交互动数', -- 社交互动数 -- 公共字段 - create_by VARCHAR(36), -- 创建人ID - create_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间 - update_by VARCHAR(36), -- 更新人ID - update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 更新时间 - is_deleted TINYINT DEFAULT 0, -- 是否删除: 0-未删除, 1-已删除 - remarks VARCHAR(500) -- 备注 + create_by VARCHAR(36) COMMENT '创建人ID', -- 创建人ID + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 创建时间 + update_by VARCHAR(36) COMMENT '更新人ID', -- 更新人ID + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', -- 更新时间 + is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-未删除, 1-已删除', -- 是否删除: 0-未删除, 1-已删除 + remarks VARCHAR(500) COMMENT '备注' -- 备注 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户统计表'; -- ============================================================================ diff --git a/backend-single/部署说明.md b/backend-single/部署说明.md new file mode 100644 index 0000000..9506343 --- /dev/null +++ b/backend-single/部署说明.md @@ -0,0 +1,205 @@ +# 情绪博物馆后端服务部署说明 + +## 文件说明 + +- `deploy.sh` - 本地打包脚本 +- `deploy-server.sh` - 服务器部署脚本 +- `/data/programs/emotion-museum/emotion-simple-0.0.1-SNAPSHOT.jar` - 打包后的 JAR 文件 + +## 部署步骤 + +### 1. 本地打包 + +```bash +# 在 backend-single 目录下执行 +./deploy.sh +``` + +### 2. 上传到服务器 + +将以下文件上传到服务器: +- `/data/programs/emotion-museum/emotion-simple-0.0.1-SNAPSHOT.jar` +- `deploy-server.sh` + +### 3. 服务器部署 + +```bash +# 给脚本添加执行权限 +chmod +x deploy-server.sh + +# 部署服务(默认操作) +./deploy-server.sh + +# 或者指定操作 +./deploy-server.sh deploy +``` + +## 脚本功能 + +### 主要功能 + +- **自动停止旧服务** - 安全停止正在运行的服务 +- **启动新服务** - 使用生产环境配置启动服务 +- **日志管理** - 日志保存到 `/data/logs/emotion-museum/single` +- **状态监控** - 检查服务启动状态和端口监听 +- **进程管理** - 使用 PID 文件管理进程 + +### 命令行参数 + +```bash +./deploy-server.sh [命令] + +可用命令: + deploy - 部署服务(默认) + start - 启动服务 + stop - 停止服务 + restart - 重启服务 + status - 查看服务状态 + logs - 查看实时日志 +``` + +## 配置说明 + +### 环境变量 + +脚本中的主要配置: + +```bash +APP_NAME="emotion-museum-single" # 应用名称 +JAR_NAME="emotion-simple-0.0.1-SNAPSHOT.jar" # JAR 文件名 +LOG_DIR="/data/logs/emotion-museum/single" # 日志目录 +JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC..." # JVM 参数 +``` + +### 日志文件 + +- `application.log` - 应用运行日志 +- `startup.log` - 启动日志 +- `heapdump.hprof` - 内存溢出时的堆转储文件 + +### 生产环境配置 + +服务启动时使用以下配置: +- `spring.profiles.active=prod` - 激活生产环境配置 +- `logging.file.path=/data/logs/emotion-museum/single` - 日志路径 +- `logging.file.name=/data/logs/emotion-museum/single/application.log` - 日志文件名 + +## 使用示例 + +### 首次部署 + +```bash +# 1. 上传文件到服务器 +scp /data/programs/emotion-museum/emotion-simple-0.0.1-SNAPSHOT.jar user@server:/path/to/app/ +scp deploy-server.sh user@server:/path/to/app/ + +# 2. 登录服务器 +ssh user@server + +# 3. 进入应用目录 +cd /path/to/app + +# 4. 添加执行权限 +chmod +x deploy-server.sh + +# 5. 部署服务 +./deploy-server.sh +``` + +### 日常维护 + +```bash +# 查看服务状态 +./deploy-server.sh status + +# 查看实时日志 +./deploy-server.sh logs + +# 重启服务 +./deploy-server.sh restart + +# 停止服务 +./deploy-server.sh stop +``` + +## 注意事项 + +### 1. 权限要求 + +- 确保脚本有执行权限 +- 确保有权限创建 `/data/logs/emotion-museum/single` 目录 +- 确保有权限写入日志文件 + +### 2. 端口配置 + +- 脚本默认检查 8080 端口 +- 如果使用其他端口,请修改脚本中的端口检查逻辑 + +### 3. Java 环境 + +- 确保服务器已安装 Java 8 或更高版本 +- 确保 `java` 命令在 PATH 中可用 + +### 4. 内存配置 + +- 默认配置:最小 512MB,最大 1024MB +- 根据服务器配置调整 `JAVA_OPTS` 中的内存参数 + +### 5. 日志轮转 + +建议配置日志轮转,避免日志文件过大: + +```bash +# 在 /etc/logrotate.d/ 下创建配置文件 +/data/logs/emotion-museum/single/*.log { + daily + rotate 30 + compress + delaycompress + missingok + notifempty + create 644 root root +} +``` + +## 故障排查 + +### 1. 服务启动失败 + +```bash +# 查看启动日志 +tail -f /data/logs/emotion-museum/single/startup.log + +# 查看应用日志 +tail -f /data/logs/emotion-museum/single/application.log +``` + +### 2. 端口被占用 + +```bash +# 查看端口占用情况 +netstat -tlnp | grep :8080 + +# 杀死占用端口的进程 +kill -9 +``` + +### 3. 内存不足 + +```bash +# 查看内存使用情况 +free -h + +# 调整 JAVA_OPTS 中的内存参数 +``` + +### 4. 权限问题 + +```bash +# 检查目录权限 +ls -la /data/logs/emotion-museum/single/ + +# 修改权限 +chmod 755 /data/logs/emotion-museum/single/ +chown -R user:group /data/logs/emotion-museum/single/ +``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e2d0b50 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,445 @@ +{ + "name": "EmotionMuseum", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "devDependencies": { + "typescript": "^5.8.3", + "vue-tsc": "^3.0.4" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.28.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.20", + "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.20.tgz", + "integrity": "sha512-dRDF1G33xaAIDqR6+mXUIjXYdu9vzSxlMGfMEwBxQsfY/JMUEXSpLTR057oTKlUQ2nIvCmP9k94A8h8z2VrNSA==", + "dev": true, + "dependencies": { + "@volar/source-map": "2.4.20" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.20", + "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.20.tgz", + "integrity": "sha512-mVjmFQH8mC+nUaVwmbxoYUy8cww+abaO8dWzqPUjilsavjxH0jCJ3Mp8HFuHsdewZs2c+SP+EO7hCd8Z92whJg==", + "dev": true + }, + "node_modules/@volar/typescript": { + "version": "2.4.20", + "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.20.tgz", + "integrity": "sha512-Oc4DczPwQyXcVbd+5RsNEqX6ia0+w3p+klwdZQ6ZKhFjWoBP9PCPQYlKYRi/tDemWphW93P/Vv13vcE9I9D2GQ==", + "dev": true, + "dependencies": { + "@volar/language-core": "2.4.20", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.18.tgz", + "integrity": "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.28.0", + "@vue/shared": "3.5.18", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.18.tgz", + "integrity": "sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==", + "dev": true, + "dependencies": { + "@vue/compiler-core": "3.5.18", + "@vue/shared": "3.5.18" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmmirror.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/language-core": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.0.4.tgz", + "integrity": "sha512-BvueED4LfBCSNH66eeUQk37MQCb7hjdezzGgxniM0LbriW53AJIyLorgshAtStmjfsAuOCcTl/c1b+nz/ye8xQ==", + "dev": true, + "dependencies": { + "@volar/language-core": "2.4.20", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^2.0.5", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.2" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/shared": { + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.18.tgz", + "integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==", + "dev": true + }, + "node_modules/alien-signals": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-2.0.5.tgz", + "integrity": "sha512-PdJB6+06nUNAClInE3Dweq7/2xVAYM64vvvS1IHVHSJmgeOtEdrAGyp7Z2oJtYm0B342/Exd2NT0uMJaThcjLQ==", + "dev": true + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true + }, + "node_modules/vue-tsc": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.0.4.tgz", + "integrity": "sha512-kZmSEjGtROApVBuaIcoprrXZsFNGon5ggkTJokmhQ/H1hMzCFRPQ0Ed8IHYFsmYJYvHBcdmEQVGVcRuxzPzNbw==", + "dev": true, + "dependencies": { + "@volar/typescript": "2.4.20", + "@vue/language-core": "3.0.4" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + } + }, + "dependencies": { + "@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true + }, + "@babel/parser": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "dev": true, + "requires": { + "@babel/types": "^7.28.0" + } + }, + "@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + } + }, + "@volar/language-core": { + "version": "2.4.20", + "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.20.tgz", + "integrity": "sha512-dRDF1G33xaAIDqR6+mXUIjXYdu9vzSxlMGfMEwBxQsfY/JMUEXSpLTR057oTKlUQ2nIvCmP9k94A8h8z2VrNSA==", + "dev": true, + "requires": { + "@volar/source-map": "2.4.20" + } + }, + "@volar/source-map": { + "version": "2.4.20", + "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.20.tgz", + "integrity": "sha512-mVjmFQH8mC+nUaVwmbxoYUy8cww+abaO8dWzqPUjilsavjxH0jCJ3Mp8HFuHsdewZs2c+SP+EO7hCd8Z92whJg==", + "dev": true + }, + "@volar/typescript": { + "version": "2.4.20", + "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.20.tgz", + "integrity": "sha512-Oc4DczPwQyXcVbd+5RsNEqX6ia0+w3p+klwdZQ6ZKhFjWoBP9PCPQYlKYRi/tDemWphW93P/Vv13vcE9I9D2GQ==", + "dev": true, + "requires": { + "@volar/language-core": "2.4.20", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "@vue/compiler-core": { + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.18.tgz", + "integrity": "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==", + "dev": true, + "requires": { + "@babel/parser": "^7.28.0", + "@vue/shared": "3.5.18", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "@vue/compiler-dom": { + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.18.tgz", + "integrity": "sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==", + "dev": true, + "requires": { + "@vue/compiler-core": "3.5.18", + "@vue/shared": "3.5.18" + } + }, + "@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmmirror.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "requires": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "@vue/language-core": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.0.4.tgz", + "integrity": "sha512-BvueED4LfBCSNH66eeUQk37MQCb7hjdezzGgxniM0LbriW53AJIyLorgshAtStmjfsAuOCcTl/c1b+nz/ye8xQ==", + "dev": true, + "requires": { + "@volar/language-core": "2.4.20", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^2.0.5", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.2" + } + }, + "@vue/shared": { + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.18.tgz", + "integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==", + "dev": true + }, + "alien-signals": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-2.0.5.tgz", + "integrity": "sha512-PdJB6+06nUNAClInE3Dweq7/2xVAYM64vvvS1IHVHSJmgeOtEdrAGyp7Z2oJtYm0B342/Exd2NT0uMJaThcjLQ==", + "dev": true + }, + "de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true + }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true + }, + "path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true + }, + "source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true + }, + "typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true + }, + "vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true + }, + "vue-tsc": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.0.4.tgz", + "integrity": "sha512-kZmSEjGtROApVBuaIcoprrXZsFNGon5ggkTJokmhQ/H1hMzCFRPQ0Ed8IHYFsmYJYvHBcdmEQVGVcRuxzPzNbw==", + "dev": true, + "requires": { + "@volar/typescript": "2.4.20", + "@vue/language-core": "3.0.4" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..41d039f --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "devDependencies": { + "typescript": "^5.8.3", + "vue-tsc": "^3.0.4" + } +} diff --git a/web/deploy.sh b/web/deploy.sh index 5cd449e..c8a5c91 100644 --- a/web/deploy.sh +++ b/web/deploy.sh @@ -1,344 +1,5 @@ #!/bin/bash - -# 情感博物馆前端部署脚本 -# 作者: emotion-museum -# 日期: 2025-07-18 -# 支持Jenkins CI/CD部署 - set -e - -# 配置变量 - 支持Jenkins环境变量覆盖 -REMOTE_HOST="${DEPLOY_HOST:-'root@47.111.10.27'}" -REMOTE_WEB_DIR="${REMOTE_WEB_DIR:-/data/www/emotion-museum}" -FRONTEND_DIR="web-flowith" -PROJECT_NAME="emotion-museum-frontend" - -# Jenkins构建信息 -BUILD_NUMBER="${BUILD_NUMBER:-manual}" -JOB_NAME="${JOB_NAME:-local-deploy}" -BUILD_URL="${BUILD_URL:-}" - -# 颜色输出 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -# 日志函数 -log_info() { - echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -# 检查远程服务器连接 -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 "远程服务器连接正常" - return 0 - else - log_error "无法连接到远程服务器 'root@47.111.10.27'" - return 1 - fi -} - -# 检查本地环境 -check_local_environment() { - log_info "检查本地构建环境..." - - # 检查Node.js - if ! command -v node &> /dev/null; then - log_error "Node.js 未安装" - return 1 - fi - - # 检查npm - if ! command -v npm &> /dev/null; then - log_error "npm 未安装" - return 1 - fi - - log_info "Node.js 版本: $(node --version)" - log_info "npm 版本: $(npm --version)" - log_success "本地环境检查通过" - return 0 -} - -# 安装依赖 -install_dependencies() { - log_info "安装前端依赖..." - - if [ -f "package-lock.json" ]; then - npm ci --silent - else - npm install --silent - fi - - log_success "依赖安装完成" -} - -# 构建前端项目 -build_frontend() { - log_info "构建前端项目..." - - # 设置生产环境变量 - export NODE_ENV=production - export VITE_API_BASE_URL=http://47.111.10.27:19000 - - # 执行构建 - if npm run build; then - log_success "前端项目构建成功" - - # 检查构建产物 - if [ -d "dist" ]; then - local dist_size=$(du -sh dist | cut -f1) - log_info "构建产物大小: $dist_size" - log_info "构建产物文件:" - ls -la dist/ | head -10 - else - log_error "构建产物目录不存在" - return 1 - fi - else - log_error "前端项目构建失败" - return 1 - fi -} - -# 创建远程目录 -create_remote_directories() { - log_info "创建远程目录结构..." - ssh 'root@47.111.10.27' " - mkdir -p $REMOTE_WEB_DIR - mkdir -p $REMOTE_WEB_DIR/backup - mkdir -p /data/logs/nginx - " - log_success "远程目录创建完成" -} - -# 备份旧版本 -backup_old_version() { - log_info "备份旧版本..." - - local backup_name="backup_$(date +%Y%m%d_%H%M%S)" - - ssh 'root@47.111.10.27' " - if [ -d '$REMOTE_WEB_DIR/$FRONTEND_DIR' ]; then - mv '$REMOTE_WEB_DIR/$FRONTEND_DIR' '$REMOTE_WEB_DIR/backup/$backup_name' - echo '旧版本已备份到: $REMOTE_WEB_DIR/backup/$backup_name' - - # 只保留最近5个备份 - cd '$REMOTE_WEB_DIR/backup' - ls -t | tail -n +6 | xargs -r rm -rf - else - echo '没有发现旧版本,跳过备份' - fi - " - - log_success "备份完成" -} - -# 部署前端文件 -deploy_frontend() { - log_info "部署前端文件到远程服务器..." - - # 上传构建产物 - if scp -r dist/ 'root@47.111.10.27':$REMOTE_WEB_DIR/$FRONTEND_DIR/; then - log_success "前端文件上传成功" - else - log_error "前端文件上传失败" - return 1 - fi - - # 设置文件权限 - ssh 'root@47.111.10.27' " - chown -R www-data:www-data '$REMOTE_WEB_DIR/$FRONTEND_DIR' 2>/dev/null || true - chmod -R 755 '$REMOTE_WEB_DIR/$FRONTEND_DIR' - " - - log_success "文件权限设置完成" -} - -# 配置Nginx -configure_nginx() { - log_info "配置Nginx..." - - # 创建Nginx配置 - ssh 'root@47.111.10.27' "cat > /etc/nginx/sites-available/emotion-museum << 'EOF' -server { - listen 80; - server_name 47.111.10.27; - - # 前端静态文件 - location /emotion-museum { - alias $REMOTE_WEB_DIR/$FRONTEND_DIR; - index index.html; - try_files \$uri \$uri/ /emotion-museum/index.html; - - # 静态资源缓存 - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { - expires 1y; - add_header Cache-Control \"public, immutable\"; - } - - # HTML文件不缓存 - location ~* \.html$ { - expires -1; - add_header Cache-Control \"no-cache, no-store, must-revalidate\"; - } - } - - # API代理到后端网关 - location /api/ { - proxy_pass http://127.0.0.1:19000/; - proxy_set_header Host \$host; - proxy_set_header X-Real-IP \$remote_addr; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto \$scheme; - - # WebSocket支持 - proxy_http_version 1.1; - proxy_set_header Upgrade \$http_upgrade; - proxy_set_header Connection \"upgrade\"; - } - - # 日志配置 - access_log /data/logs/nginx/emotion-museum-access.log; - error_log /data/logs/nginx/emotion-museum-error.log; -} -EOF" - - # 启用站点 - ssh 'root@47.111.10.27' " - ln -sf /etc/nginx/sites-available/emotion-museum /etc/nginx/sites-enabled/ - nginx -t && systemctl reload nginx - " 2>/dev/null || log_warning "Nginx配置可能需要手动检查" - - log_success "Nginx配置完成" -} - -# 健康检查 -health_check() { - log_info "执行健康检查..." - - sleep 3 - - # 检查前端页面 - if curl -f -s "http://47.111.10.27/emotion-museum/" > /dev/null 2>&1; then - log_success "前端页面访问正常" - else - log_warning "前端页面访问异常,请检查Nginx配置" - fi - - # 检查API代理 - if curl -f -s "http://47.111.10.27/api/user/health" > /dev/null 2>&1; then - log_success "API代理正常" - else - log_warning "API代理异常,请检查后端服务状态" - fi -} - -# 显示部署报告 -show_deployment_report() { - local total_time=$1 - - echo "" - echo "========================================" - echo " 前端部署完成报告" - echo "========================================" - echo "项目名称: $PROJECT_NAME" - echo "目标服务器: $REMOTE_HOST" - echo "部署路径: $REMOTE_WEB_DIR/$FRONTEND_DIR" - 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 " 前端页面: http://47.111.10.27/emotion-museum/" - echo " API接口: http://47.111.10.27/api/" - - echo "" - echo "📁 远程文件信息:" - ssh 'root@47.111.10.27' " - echo '部署目录大小:' - du -sh '$REMOTE_WEB_DIR/$FRONTEND_DIR' 2>/dev/null || echo '无法获取目录大小' - echo '' - echo '主要文件:' - ls -la '$REMOTE_WEB_DIR/$FRONTEND_DIR' 2>/dev/null | head -10 || echo '无法列出文件' - " - - echo "" - echo "========================================" - echo "🎉 前端部署完成!" -} - -# 主函数 -main() { - local start_time=$(date +%s) - - log_info "🚀 开始前端部署..." - log_info "目标服务器: $REMOTE_HOST" - log_info "部署路径: $REMOTE_WEB_DIR/$FRONTEND_DIR" - - # 检查环境 - if ! check_local_environment; then - log_error "本地环境检查失败" - exit 1 - fi - - if ! check_remote_connection; then - log_error "远程服务器连接失败" - exit 1 - fi - - # 安装依赖 - install_dependencies - - # 构建项目 - build_frontend - - # 创建远程目录 - create_remote_directories - - # 备份旧版本 - backup_old_version - - # 部署文件 - deploy_frontend - - # 配置Nginx - configure_nginx - - # 健康检查 - health_check - - # 计算总耗时 - local end_time=$(date +%s) - local total_time=$((end_time - start_time)) - - # 显示报告 - show_deployment_report $total_time - - log_success "🎉 前端部署完成!" -} - -# 执行主函数 -main "$@" +npm install +npm run build +echo "前端已打包,dist 目录可部署到 nginx/html 目录" diff --git a/web/package-lock.json b/web/package-lock.json index 8a2d113..577f331 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -35,7 +35,7 @@ "sass": "^1.66.0", "typescript": "^5.1.0", "vite": "^4.4.0", - "vue-tsc": "^1.8.8" + "vue-tsc": "^3.0.4" }, "engines": { "node": ">=16.0.0", @@ -98,17 +98,17 @@ } }, "node_modules/@babel/runtime": { - "version": "7.27.6", - "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.27.6.tgz", - "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", + "version": "7.28.2", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.2.tgz", + "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.28.1", - "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.1.tgz", - "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", + "version": "7.28.2", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" @@ -967,9 +967,9 @@ } }, "node_modules/@pkgr/core": { - "version": "0.2.7", - "resolved": "https://registry.npmmirror.com/@pkgr/core/-/core-0.2.7.tgz", - "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", + "version": "0.2.9", + "resolved": "https://registry.npmmirror.com/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" @@ -994,9 +994,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.19.8", - "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.8.tgz", - "integrity": "sha512-HzbgCY53T6bfu4tT7Aq3TvViJyHjLjPNaAS3HOuMc9pw97KHsUtXNX4L+wu59g1WnjsZSko35MbEqnO58rihhw==", + "version": "20.19.9", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.9.tgz", + "integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==", "dev": true, "dependencies": { "undici-types": "~6.21.0" @@ -1233,64 +1233,62 @@ } }, "node_modules/@volar/language-core": { - "version": "1.11.1", - "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-1.11.1.tgz", - "integrity": "sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==", + "version": "2.4.20", + "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.20.tgz", + "integrity": "sha512-dRDF1G33xaAIDqR6+mXUIjXYdu9vzSxlMGfMEwBxQsfY/JMUEXSpLTR057oTKlUQ2nIvCmP9k94A8h8z2VrNSA==", "dev": true, "dependencies": { - "@volar/source-map": "1.11.1" + "@volar/source-map": "2.4.20" } }, "node_modules/@volar/source-map": { - "version": "1.11.1", - "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-1.11.1.tgz", - "integrity": "sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==", - "dev": true, - "dependencies": { - "muggle-string": "^0.3.1" - } + "version": "2.4.20", + "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.20.tgz", + "integrity": "sha512-mVjmFQH8mC+nUaVwmbxoYUy8cww+abaO8dWzqPUjilsavjxH0jCJ3Mp8HFuHsdewZs2c+SP+EO7hCd8Z92whJg==", + "dev": true }, "node_modules/@volar/typescript": { - "version": "1.11.1", - "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-1.11.1.tgz", - "integrity": "sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==", + "version": "2.4.20", + "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.20.tgz", + "integrity": "sha512-Oc4DczPwQyXcVbd+5RsNEqX6ia0+w3p+klwdZQ6ZKhFjWoBP9PCPQYlKYRi/tDemWphW93P/Vv13vcE9I9D2GQ==", "dev": true, "dependencies": { - "@volar/language-core": "1.11.1", - "path-browserify": "^1.0.1" + "@volar/language-core": "2.4.20", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" } }, "node_modules/@vue/compiler-core": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.17.tgz", - "integrity": "sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==", + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.18.tgz", + "integrity": "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==", "dependencies": { - "@babel/parser": "^7.27.5", - "@vue/shared": "3.5.17", + "@babel/parser": "^7.28.0", + "@vue/shared": "3.5.18", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.17.tgz", - "integrity": "sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==", + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.18.tgz", + "integrity": "sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==", "dependencies": { - "@vue/compiler-core": "3.5.17", - "@vue/shared": "3.5.17" + "@vue/compiler-core": "3.5.18", + "@vue/shared": "3.5.18" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.17.tgz", - "integrity": "sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==", + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.18.tgz", + "integrity": "sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA==", "dependencies": { - "@babel/parser": "^7.27.5", - "@vue/compiler-core": "3.5.17", - "@vue/compiler-dom": "3.5.17", - "@vue/compiler-ssr": "3.5.17", - "@vue/shared": "3.5.17", + "@babel/parser": "^7.28.0", + "@vue/compiler-core": "3.5.18", + "@vue/compiler-dom": "3.5.18", + "@vue/compiler-ssr": "3.5.18", + "@vue/shared": "3.5.18", "estree-walker": "^2.0.2", "magic-string": "^0.30.17", "postcss": "^8.5.6", @@ -1298,12 +1296,22 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.17.tgz", - "integrity": "sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==", + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.18.tgz", + "integrity": "sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==", "dependencies": { - "@vue/compiler-dom": "3.5.17", - "@vue/shared": "3.5.17" + "@vue/compiler-dom": "3.5.18", + "@vue/shared": "3.5.18" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmmirror.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" } }, "node_modules/@vue/devtools-api": { @@ -1560,20 +1568,19 @@ } }, "node_modules/@vue/language-core": { - "version": "1.8.27", - "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-1.8.27.tgz", - "integrity": "sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==", + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.0.4.tgz", + "integrity": "sha512-BvueED4LfBCSNH66eeUQk37MQCb7hjdezzGgxniM0LbriW53AJIyLorgshAtStmjfsAuOCcTl/c1b+nz/ye8xQ==", "dev": true, "dependencies": { - "@volar/language-core": "~1.11.1", - "@volar/source-map": "~1.11.1", - "@vue/compiler-dom": "^3.3.0", - "@vue/shared": "^3.3.0", - "computeds": "^0.0.1", - "minimatch": "^9.0.3", - "muggle-string": "^0.3.1", + "@volar/language-core": "2.4.20", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^2.0.5", + "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", - "vue-template-compiler": "^2.7.14" + "picomatch": "^4.0.2" }, "peerDependencies": { "typescript": "*" @@ -1584,50 +1591,62 @@ } } }, + "node_modules/@vue/language-core/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@vue/reactivity": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.17.tgz", - "integrity": "sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==", + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.18.tgz", + "integrity": "sha512-x0vPO5Imw+3sChLM5Y+B6G1zPjwdOri9e8V21NnTnlEvkxatHEH5B5KEAJcjuzQ7BsjGrKtfzuQ5eQwXh8HXBg==", "dependencies": { - "@vue/shared": "3.5.17" + "@vue/shared": "3.5.18" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.17.tgz", - "integrity": "sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==", + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.18.tgz", + "integrity": "sha512-DUpHa1HpeOQEt6+3nheUfqVXRog2kivkXHUhoqJiKR33SO4x+a5uNOMkV487WPerQkL0vUuRvq/7JhRgLW3S+w==", "dependencies": { - "@vue/reactivity": "3.5.17", - "@vue/shared": "3.5.17" + "@vue/reactivity": "3.5.18", + "@vue/shared": "3.5.18" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.17.tgz", - "integrity": "sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==", + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.18.tgz", + "integrity": "sha512-YwDj71iV05j4RnzZnZtGaXwPoUWeRsqinblgVJwR8XTXYZ9D5PbahHQgsbmzUvCWNF6x7siQ89HgnX5eWkr3mw==", "dependencies": { - "@vue/reactivity": "3.5.17", - "@vue/runtime-core": "3.5.17", - "@vue/shared": "3.5.17", + "@vue/reactivity": "3.5.18", + "@vue/runtime-core": "3.5.18", + "@vue/shared": "3.5.18", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.17.tgz", - "integrity": "sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==", + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.18.tgz", + "integrity": "sha512-PvIHLUoWgSbDG7zLHqSqaCoZvHi6NNmfVFOqO+OnwvqMz/tqQr3FuGWS8ufluNddk7ZLBJYMrjcw1c6XzR12mA==", "dependencies": { - "@vue/compiler-ssr": "3.5.17", - "@vue/shared": "3.5.17" + "@vue/compiler-ssr": "3.5.18", + "@vue/shared": "3.5.18" }, "peerDependencies": { - "vue": "3.5.17" + "vue": "3.5.18" } }, "node_modules/@vue/shared": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.17.tgz", - "integrity": "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==" + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.18.tgz", + "integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==" }, "node_modules/acorn": { "version": "8.15.0", @@ -1666,6 +1685,12 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/alien-signals": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-2.0.5.tgz", + "integrity": "sha512-PdJB6+06nUNAClInE3Dweq7/2xVAYM64vvvS1IHVHSJmgeOtEdrAGyp7Z2oJtYm0B342/Exd2NT0uMJaThcjLQ==", + "dev": true + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -1760,12 +1785,12 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.10.0", - "resolved": "https://registry.npmmirror.com/axios/-/axios-1.10.0.tgz", - "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "version": "1.11.0", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -1912,12 +1937,6 @@ "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==" }, - "node_modules/computeds": { - "version": "0.0.1", - "resolved": "https://registry.npmmirror.com/computeds/-/computeds-0.0.1.tgz", - "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==", - "dev": true - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", @@ -2278,9 +2297,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "8.10.0", - "resolved": "https://registry.npmmirror.com/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", - "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", + "version": "8.10.2", + "resolved": "https://registry.npmmirror.com/eslint-config-prettier/-/eslint-config-prettier-8.10.2.tgz", + "integrity": "sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -2290,9 +2309,9 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.5.1", - "resolved": "https://registry.npmmirror.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz", - "integrity": "sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==", + "version": "5.5.3", + "resolved": "https://registry.npmmirror.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.3.tgz", + "integrity": "sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==", "dev": true, "dependencies": { "prettier-linter-helpers": "^1.0.0", @@ -2649,9 +2668,9 @@ } }, "node_modules/form-data": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.3.tgz", - "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -3184,9 +3203,9 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/muggle-string": { - "version": "0.3.1", - "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.3.1.tgz", - "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==", + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", "dev": true }, "node_modules/nanoid": { @@ -3797,12 +3816,12 @@ } }, "node_modules/synckit": { - "version": "0.11.8", - "resolved": "https://registry.npmmirror.com/synckit/-/synckit-0.11.8.tgz", - "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", + "version": "0.11.11", + "resolved": "https://registry.npmmirror.com/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", "dev": true, "dependencies": { - "@pkgr/core": "^0.2.4" + "@pkgr/core": "^0.2.9" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -4020,16 +4039,22 @@ } } }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true + }, "node_modules/vue": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.17.tgz", - "integrity": "sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==", + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.18.tgz", + "integrity": "sha512-7W4Y4ZbMiQ3SEo+m9lnoNpV9xG7QVMLa+/0RFwwiAVkeYoyGXqWE85jabU4pllJNUzqfLShJ5YLptewhCWUgNA==", "dependencies": { - "@vue/compiler-dom": "3.5.17", - "@vue/compiler-sfc": "3.5.17", - "@vue/runtime-dom": "3.5.17", - "@vue/server-renderer": "3.5.17", - "@vue/shared": "3.5.17" + "@vue/compiler-dom": "3.5.18", + "@vue/compiler-sfc": "3.5.18", + "@vue/runtime-dom": "3.5.18", + "@vue/server-renderer": "3.5.18", + "@vue/shared": "3.5.18" }, "peerDependencies": { "typescript": "*" @@ -4112,31 +4137,20 @@ "vue": "^3.2.0" } }, - "node_modules/vue-template-compiler": { - "version": "2.7.16", - "resolved": "https://registry.npmmirror.com/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", - "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", - "dev": true, - "dependencies": { - "de-indent": "^1.0.2", - "he": "^1.2.0" - } - }, "node_modules/vue-tsc": { - "version": "1.8.27", - "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-1.8.27.tgz", - "integrity": "sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==", + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.0.4.tgz", + "integrity": "sha512-kZmSEjGtROApVBuaIcoprrXZsFNGon5ggkTJokmhQ/H1hMzCFRPQ0Ed8IHYFsmYJYvHBcdmEQVGVcRuxzPzNbw==", "dev": true, "dependencies": { - "@volar/typescript": "~1.11.1", - "@vue/language-core": "1.8.27", - "semver": "^7.5.4" + "@volar/typescript": "2.4.20", + "@vue/language-core": "3.0.4" }, "bin": { "vue-tsc": "bin/vue-tsc.js" }, "peerDependencies": { - "typescript": "*" + "typescript": ">=5.0.0" } }, "node_modules/vue-types": { @@ -4318,14 +4332,14 @@ } }, "@babel/runtime": { - "version": "7.27.6", - "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.27.6.tgz", - "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==" + "version": "7.28.2", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.2.tgz", + "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==" }, "@babel/types": { - "version": "7.28.1", - "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.1.tgz", - "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", + "version": "7.28.2", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "requires": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" @@ -4757,9 +4771,9 @@ "optional": true }, "@pkgr/core": { - "version": "0.2.7", - "resolved": "https://registry.npmmirror.com/@pkgr/core/-/core-0.2.7.tgz", - "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", + "version": "0.2.9", + "resolved": "https://registry.npmmirror.com/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true }, "@simonwep/pickr": { @@ -4778,9 +4792,9 @@ "dev": true }, "@types/node": { - "version": "20.19.8", - "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.8.tgz", - "integrity": "sha512-HzbgCY53T6bfu4tT7Aq3TvViJyHjLjPNaAS3HOuMc9pw97KHsUtXNX4L+wu59g1WnjsZSko35MbEqnO58rihhw==", + "version": "20.19.9", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.9.tgz", + "integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==", "dev": true, "requires": { "undici-types": "~6.21.0" @@ -4922,64 +4936,62 @@ "requires": {} }, "@volar/language-core": { - "version": "1.11.1", - "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-1.11.1.tgz", - "integrity": "sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==", + "version": "2.4.20", + "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.20.tgz", + "integrity": "sha512-dRDF1G33xaAIDqR6+mXUIjXYdu9vzSxlMGfMEwBxQsfY/JMUEXSpLTR057oTKlUQ2nIvCmP9k94A8h8z2VrNSA==", "dev": true, "requires": { - "@volar/source-map": "1.11.1" + "@volar/source-map": "2.4.20" } }, "@volar/source-map": { - "version": "1.11.1", - "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-1.11.1.tgz", - "integrity": "sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==", - "dev": true, - "requires": { - "muggle-string": "^0.3.1" - } + "version": "2.4.20", + "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.20.tgz", + "integrity": "sha512-mVjmFQH8mC+nUaVwmbxoYUy8cww+abaO8dWzqPUjilsavjxH0jCJ3Mp8HFuHsdewZs2c+SP+EO7hCd8Z92whJg==", + "dev": true }, "@volar/typescript": { - "version": "1.11.1", - "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-1.11.1.tgz", - "integrity": "sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==", + "version": "2.4.20", + "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.20.tgz", + "integrity": "sha512-Oc4DczPwQyXcVbd+5RsNEqX6ia0+w3p+klwdZQ6ZKhFjWoBP9PCPQYlKYRi/tDemWphW93P/Vv13vcE9I9D2GQ==", "dev": true, "requires": { - "@volar/language-core": "1.11.1", - "path-browserify": "^1.0.1" + "@volar/language-core": "2.4.20", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" } }, "@vue/compiler-core": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.17.tgz", - "integrity": "sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==", + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.18.tgz", + "integrity": "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==", "requires": { - "@babel/parser": "^7.27.5", - "@vue/shared": "3.5.17", + "@babel/parser": "^7.28.0", + "@vue/shared": "3.5.18", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "@vue/compiler-dom": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.17.tgz", - "integrity": "sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==", + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.18.tgz", + "integrity": "sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==", "requires": { - "@vue/compiler-core": "3.5.17", - "@vue/shared": "3.5.17" + "@vue/compiler-core": "3.5.18", + "@vue/shared": "3.5.18" } }, "@vue/compiler-sfc": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.17.tgz", - "integrity": "sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==", + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.18.tgz", + "integrity": "sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA==", "requires": { - "@babel/parser": "^7.27.5", - "@vue/compiler-core": "3.5.17", - "@vue/compiler-dom": "3.5.17", - "@vue/compiler-ssr": "3.5.17", - "@vue/shared": "3.5.17", + "@babel/parser": "^7.28.0", + "@vue/compiler-core": "3.5.18", + "@vue/compiler-dom": "3.5.18", + "@vue/compiler-ssr": "3.5.18", + "@vue/shared": "3.5.18", "estree-walker": "^2.0.2", "magic-string": "^0.30.17", "postcss": "^8.5.6", @@ -4987,12 +4999,22 @@ } }, "@vue/compiler-ssr": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.17.tgz", - "integrity": "sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==", + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.18.tgz", + "integrity": "sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==", "requires": { - "@vue/compiler-dom": "3.5.17", - "@vue/shared": "3.5.17" + "@vue/compiler-dom": "3.5.18", + "@vue/shared": "3.5.18" + } + }, + "@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmmirror.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "requires": { + "de-indent": "^1.0.2", + "he": "^1.2.0" } }, "@vue/devtools-api": { @@ -5139,63 +5161,70 @@ } }, "@vue/language-core": { - "version": "1.8.27", - "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-1.8.27.tgz", - "integrity": "sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==", + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.0.4.tgz", + "integrity": "sha512-BvueED4LfBCSNH66eeUQk37MQCb7hjdezzGgxniM0LbriW53AJIyLorgshAtStmjfsAuOCcTl/c1b+nz/ye8xQ==", "dev": true, "requires": { - "@volar/language-core": "~1.11.1", - "@volar/source-map": "~1.11.1", - "@vue/compiler-dom": "^3.3.0", - "@vue/shared": "^3.3.0", - "computeds": "^0.0.1", - "minimatch": "^9.0.3", - "muggle-string": "^0.3.1", + "@volar/language-core": "2.4.20", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^2.0.5", + "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", - "vue-template-compiler": "^2.7.14" + "picomatch": "^4.0.2" + }, + "dependencies": { + "picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true + } } }, "@vue/reactivity": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.17.tgz", - "integrity": "sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==", + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.18.tgz", + "integrity": "sha512-x0vPO5Imw+3sChLM5Y+B6G1zPjwdOri9e8V21NnTnlEvkxatHEH5B5KEAJcjuzQ7BsjGrKtfzuQ5eQwXh8HXBg==", "requires": { - "@vue/shared": "3.5.17" + "@vue/shared": "3.5.18" } }, "@vue/runtime-core": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.17.tgz", - "integrity": "sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==", + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.18.tgz", + "integrity": "sha512-DUpHa1HpeOQEt6+3nheUfqVXRog2kivkXHUhoqJiKR33SO4x+a5uNOMkV487WPerQkL0vUuRvq/7JhRgLW3S+w==", "requires": { - "@vue/reactivity": "3.5.17", - "@vue/shared": "3.5.17" + "@vue/reactivity": "3.5.18", + "@vue/shared": "3.5.18" } }, "@vue/runtime-dom": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.17.tgz", - "integrity": "sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==", + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.18.tgz", + "integrity": "sha512-YwDj71iV05j4RnzZnZtGaXwPoUWeRsqinblgVJwR8XTXYZ9D5PbahHQgsbmzUvCWNF6x7siQ89HgnX5eWkr3mw==", "requires": { - "@vue/reactivity": "3.5.17", - "@vue/runtime-core": "3.5.17", - "@vue/shared": "3.5.17", + "@vue/reactivity": "3.5.18", + "@vue/runtime-core": "3.5.18", + "@vue/shared": "3.5.18", "csstype": "^3.1.3" } }, "@vue/server-renderer": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.17.tgz", - "integrity": "sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==", + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.18.tgz", + "integrity": "sha512-PvIHLUoWgSbDG7zLHqSqaCoZvHi6NNmfVFOqO+OnwvqMz/tqQr3FuGWS8ufluNddk7ZLBJYMrjcw1c6XzR12mA==", "requires": { - "@vue/compiler-ssr": "3.5.17", - "@vue/shared": "3.5.17" + "@vue/compiler-ssr": "3.5.18", + "@vue/shared": "3.5.18" } }, "@vue/shared": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.17.tgz", - "integrity": "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==" + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.18.tgz", + "integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==" }, "acorn": { "version": "8.15.0", @@ -5222,6 +5251,12 @@ "uri-js": "^4.2.2" } }, + "alien-signals": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-2.0.5.tgz", + "integrity": "sha512-PdJB6+06nUNAClInE3Dweq7/2xVAYM64vvvS1IHVHSJmgeOtEdrAGyp7Z2oJtYm0B342/Exd2NT0uMJaThcjLQ==", + "dev": true + }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -5294,12 +5329,12 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "axios": { - "version": "1.10.0", - "resolved": "https://registry.npmmirror.com/axios/-/axios-1.10.0.tgz", - "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "version": "1.11.0", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "requires": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -5412,12 +5447,6 @@ "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==" }, - "computeds": { - "version": "0.0.1", - "resolved": "https://registry.npmmirror.com/computeds/-/computeds-0.0.1.tgz", - "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==", - "dev": true - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", @@ -5708,16 +5737,16 @@ } }, "eslint-config-prettier": { - "version": "8.10.0", - "resolved": "https://registry.npmmirror.com/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", - "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", + "version": "8.10.2", + "resolved": "https://registry.npmmirror.com/eslint-config-prettier/-/eslint-config-prettier-8.10.2.tgz", + "integrity": "sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A==", "dev": true, "requires": {} }, "eslint-plugin-prettier": { - "version": "5.5.1", - "resolved": "https://registry.npmmirror.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz", - "integrity": "sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==", + "version": "5.5.3", + "resolved": "https://registry.npmmirror.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.3.tgz", + "integrity": "sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==", "dev": true, "requires": { "prettier-linter-helpers": "^1.0.0", @@ -5954,9 +5983,9 @@ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==" }, "form-data": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.3.tgz", - "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -6353,9 +6382,9 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "muggle-string": { - "version": "0.3.1", - "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.3.1.tgz", - "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==", + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", "dev": true }, "nanoid": { @@ -6748,12 +6777,12 @@ } }, "synckit": { - "version": "0.11.8", - "resolved": "https://registry.npmmirror.com/synckit/-/synckit-0.11.8.tgz", - "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", + "version": "0.11.11", + "resolved": "https://registry.npmmirror.com/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", "dev": true, "requires": { - "@pkgr/core": "^0.2.4" + "@pkgr/core": "^0.2.9" } }, "text-table": { @@ -6885,16 +6914,22 @@ "rollup": "^3.27.1" } }, + "vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true + }, "vue": { - "version": "3.5.17", - "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.17.tgz", - "integrity": "sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==", + "version": "3.5.18", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.18.tgz", + "integrity": "sha512-7W4Y4ZbMiQ3SEo+m9lnoNpV9xG7QVMLa+/0RFwwiAVkeYoyGXqWE85jabU4pllJNUzqfLShJ5YLptewhCWUgNA==", "requires": { - "@vue/compiler-dom": "3.5.17", - "@vue/compiler-sfc": "3.5.17", - "@vue/runtime-dom": "3.5.17", - "@vue/server-renderer": "3.5.17", - "@vue/shared": "3.5.17" + "@vue/compiler-dom": "3.5.18", + "@vue/compiler-sfc": "3.5.18", + "@vue/runtime-dom": "3.5.18", + "@vue/server-renderer": "3.5.18", + "@vue/shared": "3.5.18" } }, "vue-chartjs": { @@ -6932,25 +6967,14 @@ "@vue/devtools-api": "^6.6.4" } }, - "vue-template-compiler": { - "version": "2.7.16", - "resolved": "https://registry.npmmirror.com/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", - "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", - "dev": true, - "requires": { - "de-indent": "^1.0.2", - "he": "^1.2.0" - } - }, "vue-tsc": { - "version": "1.8.27", - "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-1.8.27.tgz", - "integrity": "sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==", + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.0.4.tgz", + "integrity": "sha512-kZmSEjGtROApVBuaIcoprrXZsFNGon5ggkTJokmhQ/H1hMzCFRPQ0Ed8IHYFsmYJYvHBcdmEQVGVcRuxzPzNbw==", "dev": true, "requires": { - "@volar/typescript": "~1.11.1", - "@vue/language-core": "1.8.27", - "semver": "^7.5.4" + "@volar/typescript": "2.4.20", + "@vue/language-core": "3.0.4" } }, "vue-types": { diff --git a/web/package.json b/web/package.json index 86809cb..8f25c23 100644 --- a/web/package.json +++ b/web/package.json @@ -12,17 +12,17 @@ "type-check": "vue-tsc --noEmit" }, "dependencies": { - "vue": "^3.3.4", - "vue-router": "^4.2.4", - "pinia": "^2.1.6", - "ant-design-vue": "^4.0.0", "@ant-design/icons-vue": "^7.0.0", + "ant-design-vue": "^4.0.0", "axios": "^1.5.0", - "dayjs": "^1.11.9", "chart.js": "^4.3.0", - "vue-chartjs": "^5.2.0", + "dayjs": "^1.11.9", + "pinia": "^2.1.6", "sockjs-client": "^1.6.1", - "stompjs": "^2.3.3" + "stompjs": "^2.3.3", + "vue": "^3.3.4", + "vue-chartjs": "^5.2.0", + "vue-router": "^4.2.4" }, "devDependencies": { "@types/node": "^20.5.0", @@ -39,7 +39,7 @@ "sass": "^1.66.0", "typescript": "^5.1.0", "vite": "^4.4.0", - "vue-tsc": "^1.8.8" + "vue-tsc": "^3.0.4" }, "engines": { "node": ">=16.0.0", diff --git a/web/src/App.vue b/web/src/App.vue index 2585f70..5c66d45 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -9,13 +9,12 @@