diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..7a12ed4f8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,69 @@ +# CI 流程 - Pull Request 自动检测 +# 当有 PR 提交或推送到 main 分支时自动运行 + +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + # ==================== 核心检查(必须通过)==================== + check: + name: ✅ 代码检查 + runs-on: ubuntu-latest + + steps: + - name: 📥 检出代码 + uses: actions/checkout@v4 + + - name: 🐍 设置 Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + cache: 'pip' + + - name: 📦 安装依赖 + run: | + pip install --upgrade pip + pip install -r requirements.txt + pip install flake8 + + - name: 🐍 语法检查 + run: | + python -m py_compile main.py config.py analyzer.py notification.py + python -m py_compile storage.py scheduler.py search_service.py + python -m py_compile market_analyzer.py stock_analyzer.py + python -m py_compile data_provider/*.py + echo "✅ Python 语法检查通过" + + - name: 🔎 静态分析 (严重错误) + run: | + # 只检查严重错误:语法错误、未定义变量等 + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + + - name: ✅ 模块导入测试 + run: | + python -c "from config import get_config; print('✅ config')" + python -c "from storage import DatabaseManager; print('✅ storage')" + python -c "from notification import NotificationService; print('✅ notification')" + python -c "from data_provider import DataFetcherManager; print('✅ data_provider')" + python -c "from analyzer import GeminiAnalyzer; print('✅ analyzer')" + echo "✅ 所有模块导入成功" + + # ==================== Docker 构建测试 ==================== + docker: + name: 🐳 Docker 构建 + runs-on: ubuntu-latest + needs: [check] + + steps: + - name: 📥 检出代码 + uses: actions/checkout@v4 + + - name: 🐳 构建镜像 + run: | + docker build -t stock-analysis:test . + docker run --rm stock-analysis:test python -c "print('✅ Docker OK')" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f7f38a85a..96858ef1e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,6 +72,36 @@ docs: 更新 README 部署说明 - 重要逻辑添加注释 - 新功能需要更新相关文档 +### CI 自动检查 + +提交 PR 后,CI 会自动运行以下检查: + +| 检查项 | 说明 | 必须通过 | +|--------|------|:--------:| +| 🐍 语法检查 | Python 语法正确性 | ✅ | +| 📦 依赖安装 | Python 3.10/3.11/3.12 多版本测试 | ✅ | +| 🐳 Docker 构建 | Docker 镜像能正常构建 | ✅ | +| 🔍 代码规范 | Black/Flake8/isort 格式检查 | ⚠️ 警告 | +| 🔒 安全检查 | Bandit/Safety 漏洞扫描 | ⚠️ 警告 | +| 🧪 单元测试 | pytest 测试(如有) | ✅ | + +**本地运行检查:** + +```bash +# 安装检查工具 +pip install black flake8 isort bandit + +# 代码格式化 +black . +isort . + +# 静态检查 +flake8 . + +# 安全扫描 +bandit -r . -x ./test_*.py +``` + ## 📋 优先贡献方向 查看 [Roadmap](README.md#-roadmap) 了解当前需要的功能: diff --git a/README.md b/README.md index 568c649f7..99bc94460 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # 📈 A股智能分析系统 [![GitHub stars](https://img.shields.io/github/stars/ZhuLinsen/daily_stock_analysis?style=social)](https://github.com/ZhuLinsen/daily_stock_analysis/stargazers) +[![CI](https://github.com/ZhuLinsen/daily_stock_analysis/actions/workflows/ci.yml/badge.svg)](https://github.com/ZhuLinsen/daily_stock_analysis/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/) [![GitHub Actions](https://img.shields.io/badge/GitHub%20Actions-Ready-2088FF?logo=github-actions&logoColor=white)](https://github.com/features/actions) @@ -68,7 +69,7 @@ | `TELEGRAM_CHAT_ID` | Telegram Chat ID | 可选 | | `EMAIL_SENDER` | 发件人邮箱(如 `xxx@qq.com`) | 可选 | | `EMAIL_PASSWORD` | 邮箱授权码(非登录密码) | 可选 | -| `EMAIL_RECEIVERS` | 收件人邮箱(留空则发给自己) | 可选 | +| `EMAIL_RECEIVERS` | 收件人邮箱(多个用逗号分隔,留空则发给自己) | 可选 | | `CUSTOM_WEBHOOK_URLS` | 自定义 Webhook(多个用逗号分隔) | 可选 | > *注:至少配置一个渠道,配置多个则同时推送到所有渠道 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..5a3a81c52 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,28 @@ +[tool.black] +line-length = 120 +target-version = ['py310', 'py311', 'py312'] +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | venv + | _build + | buck-out + | build + | dist + | __pycache__ +)/ +''' + +[tool.isort] +profile = "black" +line_length = 120 +skip = [".git", "__pycache__", ".env", "venv", ".venv"] + +[tool.bandit] +exclude_dirs = ["tests", "test_*.py"] +skips = ["B101"] # assert 语句在测试中是允许的 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..54feecbd4 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,28 @@ +[flake8] +max-line-length = 120 +exclude = + .git, + __pycache__, + .env, + venv, + .venv, + build, + dist, + *.egg-info +# E501: 行太长(有些地方确实需要长行) +# W503: 运算符在换行前(与 black 冲突) +# E203: 切片前的空格(与 black 冲突) +# E402: 模块级导入不在文件顶部(有时需要先设置环境变量) +ignore = E501,W503,E203,E402 + +[tool:pytest] +testpaths = . +python_files = test_*.py +python_functions = test_* +addopts = -v --tb=short + +[isort] +profile = black +line_length = 120 +skip = .git,__pycache__,.env,venv,.venv +known_first_party = config,storage,analyzer,notification,scheduler,search_service,market_analyzer,stock_analyzer,data_provider