diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..e868f90 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,18 @@ +{ + "permissions": { + "allow": [ + "Bash(docker build:*)", + "Bash(npm install)", + "Bash(mkdir:*)", + "Bash(docker run:*)", + "Bash(docker logs:*)", + "Bash(npm run build:*)", + "Bash(docker stop:*)", + "Bash(docker rm:*)", + "Bash(docker tag:*)", + "Bash(docker push:*)", + "Bash(docker login:*)" + ], + "deny": [] + } +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 27fa016..04aff9f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # 阶段一: 构建Vue前端 -FROM node:18-alpine as frontend-builder +FROM node:18-alpine AS frontend-builder # 设置工作目录 WORKDIR /app/frontend @@ -13,18 +13,21 @@ RUN npm ci # 复制前端源代码 COPY frontend/ ./ +# 确保node_modules中的可执行文件有正确权限 +RUN chmod +x node_modules/.bin/* + # 构建前端应用 RUN npm run build # 阶段二: 构建Python后端 -FROM python:3.10-slim as backend-builder +FROM python:3.10-slim AS backend-builder # 设置工作目录 WORKDIR /app # 安装系统依赖和构建依赖 RUN apt-get update && apt-get install -y \ - libgl1-mesa-glx \ + libgl1 \ ca-certificates \ build-essential \ && rm -rf /var/lib/apt/lists/* @@ -43,7 +46,7 @@ WORKDIR /app # 安装运行时依赖 RUN apt-get update && apt-get install -y \ - libgl1-mesa-glx \ + libgl1 \ ca-certificates \ && rm -rf /var/lib/apt/lists/* diff --git a/Dockerfile.backend b/Dockerfile.backend new file mode 100644 index 0000000..a1bb41f --- /dev/null +++ b/Dockerfile.backend @@ -0,0 +1,29 @@ +# 简化版本:只构建后端,不包含前端 +FROM python:3.10-slim + +# 设置工作目录 +WORKDIR /app + +# 复制requirements.txt +COPY requirements.txt . + +# 安装Python依赖 +RUN pip install --no-cache-dir -r requirements.txt + +# 复制后端代码 +COPY *.py ./ +COPY services/ ./services/ +COPY utils/ ./utils/ + +# 创建数据目录和日志目录 +RUN mkdir -p data logs + +# 暴露端口 +EXPOSE 8888 + +# 设置环境变量 +ENV PYTHONPATH=/app +ENV PYTHONUNBUFFERED=1 + +# 启动应用 +CMD ["python", "web_server.py"] \ No newline at end of file diff --git a/Dockerfile.full b/Dockerfile.full new file mode 100644 index 0000000..d0d8d4a --- /dev/null +++ b/Dockerfile.full @@ -0,0 +1,32 @@ +# 完整版本:包含前端和后端 +FROM python:3.10-slim + +# 设置工作目录 +WORKDIR /app + +# 复制requirements.txt +COPY requirements.txt . + +# 安装Python依赖 +RUN pip install --no-cache-dir -r requirements.txt + +# 复制后端代码 +COPY *.py ./ +COPY services/ ./services/ +COPY utils/ ./utils/ + +# 复制前端构建产物 +COPY frontend/dist/ ./frontend/dist/ + +# 创建数据目录和日志目录 +RUN mkdir -p data logs + +# 暴露端口 +EXPOSE 8888 + +# 设置环境变量 +ENV PYTHONPATH=/app +ENV PYTHONUNBUFFERED=1 + +# 启动应用 +CMD ["python", "web_server.py"] \ No newline at end of file diff --git a/docker-compose.local.yml b/docker-compose.local.yml new file mode 100644 index 0000000..7fda9c5 --- /dev/null +++ b/docker-compose.local.yml @@ -0,0 +1,27 @@ +version: '3.8' + +services: + app: + build: + context: . + dockerfile: Dockerfile + container_name: stock-scanner-app-local + ports: + - "8999:8888" + environment: + - API_KEY=${API_KEY} + - API_URL=${API_URL} + - API_MODEL=${API_MODEL} + - API_TIMEOUT=${API_TIMEOUT} + - LOGIN_PASSWORD=${LOGIN_PASSWORD} + - ANNOUNCEMENT_TEXT=${ANNOUNCEMENT_TEXT} + volumes: + - ./logs:/app/logs + - ./data:/app/data + restart: unless-stopped + networks: + - stock-scanner-network + +networks: + stock-scanner-network: + driver: bridge \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index c691ccc..886e108 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,7 @@ services: build: context: . dockerfile: Dockerfile + platform: linux/amd64 container_name: stock-scanner-app ports: - "8888:8888" diff --git a/requirements.txt b/requirements.txt index d340a20..d26c846 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ pandas==2.2.2 scipy==1.15.1 # 数据获取和分析库 -akshare==1.16.35 +akshare==1.17.44 tqdm==4.67.1 # Web框架与异步处理 diff --git a/services/ai_analyzer.py b/services/ai_analyzer.py index 57c9474..e32926c 100644 --- a/services/ai_analyzer.py +++ b/services/ai_analyzer.py @@ -266,13 +266,17 @@ class AIAnalyzer: chunk_data = json.loads(line) # 检查是否有finish_reason - finish_reason = chunk_data.get("choices", [{}])[0].get("finish_reason") + choices = chunk_data.get("choices", []) + if not choices: + logger.debug("收到空的choices数组,跳过") + continue + finish_reason = choices[0].get("finish_reason") if finish_reason == "stop": logger.debug("收到finish_reason=stop,流结束") continue # 获取delta内容 - delta = chunk_data.get("choices", [{}])[0].get("delta", {}) + delta = choices[0].get("delta", {}) # 检查delta是否为空对象 if not delta or delta == {}: @@ -350,7 +354,16 @@ class AIAnalyzer: return response_data = response.json() - analysis_text = response_data.get("choices", [{}])[0].get("message", {}).get("content", "") + choices = response_data.get("choices", []) + if not choices: + logger.error("API响应中没有choices数据") + yield json.dumps({ + "stock_code": stock_code, + "error": "API响应格式错误:缺少choices数据", + "status": "error" + }) + return + analysis_text = choices[0].get("message", {}).get("content", "") # 尝试从分析内容中提取投资建议 recommendation = self._extract_recommendation(analysis_text) diff --git a/tests/test_stream.py b/tests/test_stream.py index b5f4abb..ada0a74 100644 --- a/tests/test_stream.py +++ b/tests/test_stream.py @@ -117,7 +117,7 @@ def test_api_stream(): json_data = json.loads(data_content) logger.debug(f"JSON结构: {_truncate_json_for_logging(json_data)}") - if 'choices' in json_data: + if 'choices' in json_data and json_data['choices']: delta = json_data['choices'][0].get('delta', {}) content = delta.get('content', '')