chore: P1 工程化工具链

- pyproject.toml:pytest/black/isort/mypy 统一配置
- requirements-dev.txt:测试与代码规范开发依赖
- .flake8 / .editorconfig:PEP8 与编辑器统一
- .pre-commit-config.yaml:提交前钩子
- Makefile:常用命令集
- .env.example:环境变量模板(76 键)
- tests/:pytest 骨架 + R 响应格式冒烟测试

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
DjangoAdmin Dev
2026-08-07 12:14:51 +08:00
co-authored by Claude
parent 15f2a5e097
commit 8691412ec6
12 changed files with 448 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
# ============================================================
# 编辑器统一配置
# ============================================================
root = true
[*]
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
[*.py]
indent_size = 4
max_line_length = 120
[*.{yml,yaml}]
indent_size = 2
[*.json]
indent_size = 2
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
+125
View File
@@ -0,0 +1,125 @@
# ============================================================
# DjangoAdmin Flask+EleVue 环境变量模板
# ============================================================
# 使用方法:
# 1. 复制为 .env:cp .env.example .env
# 2. 填写 <占位符> 标记的敏感值(严禁将真实 .env 提交到版本库)
# 说明:.env 已被 .gitignore 忽略
# ============================================================
# ============================================================
# 应用基础配置(config/app.py)
# ============================================================
FLASK_NAME=Flask+EleVue旗舰版
FLASK_VERSION=v2.8.0
FLASK_APP=app.py
FLASK_ENV=development # development / production
FLASK_DEBUG=True # 生产环境必须为 False
FLASK_USE_RELOADER=False
FLASK_HOST=127.0.0.1
FLASK_PORT=8021
FLASK_SECRET_KEY=<必填,长度至少16字符,如生成随机串>
FLASK_DEMO=False # True=演示模式,拦截写操作
# 站点域名(定时任务等内部 HTTP 调用使用)
# FLASK_SITE_DOMAIN=http://127.0.0.1:8021
# 跨域白名单(多个用英文逗号分隔;配置后 CORS 允许携带凭证)
CORS_ALLOWED_ORIGINS=http://localhost:8001
# 上传配置
FLASK_UPLOAD_DIR=./uploads
FLASK_FILE_URL=http://images.flask.elevue
UPLOAD_ALLOWED_EXTS=.jpg,.jpeg,.png,.gif,.bmp,.webp,.svg,.ico,.txt,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.mp3,.mp4,.zip,.rar,.7z
UPLOAD_MAX_SIZE_MB=10
# 反向代理层数(0=无代理;1=前面有 1 层 Nginx)
PROXY_NUM=0
# ============================================================
# 数据库配置(config/database.py)
# ============================================================
DB_DRIVER=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=djangoadmin.flask.elevue
DB_USERNAME=root
DB_PASSWORD=<必填>
DB_PREFIX=flask_
DB_DEBUG=True
DB_SSL=False
# 若开启 DB_SSL=True,配置以下证书路径
# DB_SSL_CA=
# DB_SSL_CERT=
# DB_SSL_KEY=
# ============================================================
# Redis 缓存(config/redis.py)
# ============================================================
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=<如有则填写,否则留空>
REDIS_INDEX=0
REDIS_AUTH=True # 是否启用密码认证
REDIS_DECODE_RESPONSES=True
# ============================================================
# JWT / 权限 / 白名单(config/auth.py)
# ============================================================
JWT_SALT=<必填,JWT 签名密钥,建议随机长串>
JWT_EXPIRE_MINUTES=20
LOGIN_FAIL_MAX=5
LOGIN_LOCK_SECONDS=300
PERMISSION_ENABLED=True # 权限校验总开关(开发可关)
# 内部调用密钥(定时任务等无用户场景,通过 X-Scheduler-Key / X-Internal-API-Key 传递)
INTERNAL_API_KEY=<必填>
INTERNAL_ALLOWED_IPS=127.0.0.1
# 登录白名单(以下路径跳过登录校验)
LOGIN_WHITELIST_EXACT=/login,/captcha,/favicon.ico
LOGIN_WHITELIST_PREFIX=/job/sendSms,/public,/static,/api/webhook
LOGIN_WHITELIST_WILDCARD=/job/*/send*,/api/v1/*/public*
LOGIN_WHITELIST_DEBUG=True
LOGIN_WHITELIST_LOG_ACCESS=True
# 验证码万能码(仅 FLASK_DEMO=True 时生效)
CAPTCHA_BYPASS_CODE=
# ============================================================
# 邮件(config/mail.py)
# ============================================================
MAIL_MAILER=smtp
MAIL_SERVER=smtp.163.com
MAIL_PORT=465
MAIL_USE_SSL=True
MAIL_ENCRYPTION=
MAIL_USERNAME=<必填>
MAIL_PASSWORD=<必填>
MAIL_FROM_NAME=
MAIL_FROM_ADDRESS=
# ============================================================
# 短信(config/sms.py,阿里云短信)
# ============================================================
SMS_PROVIDER=aliyun
SMS_ALIYUN_ACCESS_KEY_ID=<必填>
SMS_ALIYUN_ACCESS_KEY_SECRET=<必填>
SMS_ALIYUN_SIGN_NAME=<必填>
SMS_ALIYUN_ENDPOINT=dysmsapi.aliyuncs.com
SMS_ALIYUN_CONNECT_TIMEOUT=10000
SMS_ALIYUN_READ_TIMEOUT=10000
# ============================================================
# 验证码图片(config/captcha.py)
# ============================================================
CAPTCHA_LENGTH=6
CAPTCHA_EXPIRE=300
CAPTCHA_RATE_LIMIT=10
CAPTCHA_RATE_WINDOW=60
CAPTCHA_CHARS=ABCDEFGHJKLMNPQRSTUVWXYZ2346789
CAPTCHA_FONT_PATH=static/fonts/Vera.ttf
CAPTCHA_IMAGE_WIDTH=160
CAPTCHA_IMAGE_HEIGHT=50
CAPTCHA_FONT_SIZE=28
CAPTCHA_NOISE_LINES=6
CAPTCHA_NOISE_DOTS=30
+31
View File
@@ -0,0 +1,31 @@
# ============================================================
# flake8 配置(PEP8 检查)
# 与 black(line-length=120) / isort(profile=black) 保持协调
# ============================================================
[flake8]
# 行宽:与 black 一致;中文注释/长 URL 场景已由 black 统一管理格式
max-line-length = 120
# 圈复杂度上限(存量服务函数普遍偏高,新代码应低于此值)
max-complexity = 20
# 排除目录
exclude =
.git,
__pycache__,
.venv,
venv,
migrations,
static,
uploads,
ui,
document,
.idea,
.claude,
build,
dist
# 与 black/isort 冲突项(必须忽略,否则与自动格式化互相打架)
extend-ignore =
E203, # 切片冒号前空格(black 风格)
W503, # 二元运算符换行(black 风格)
E501, # 行过长(交由 black 处理)
+39
View File
@@ -0,0 +1,39 @@
# ============================================================
# pre-commit 钩子配置
# 安装:pip install -r requirements-dev.txt && pre-commit install
# 手动全量运行:pre-commit run --all-files
# ============================================================
repos:
# ---- 通用钩子 ----
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-merge-conflict
- id: debug-statements
- id: detect-private-key
# ip2region.xdb 约 10MB,放行大文件
- id: check-added-large-files
args: ["--maxkb=12000"]
# ---- Python 格式化:black ----
- repo: https://github.com/psf/black
rev: 24.10.0
hooks:
- id: black
args: ["--line-length", "120"]
# ---- import 排序:isort ----
- repo: https://github.com/PyCQA/isort
rev: 5.13.2
hooks:
- id: isort
# ---- 静态检查:flake8 ----
- repo: https://github.com/PyCQA/flake8
rev: 7.1.1
hooks:
- id: flake8
+69
View File
@@ -0,0 +1,69 @@
# ============================================================
# DjangoAdmin Flask+EleVue 常用命令集
# 用法:make help / make lint / make test / ...
# ============================================================
PYTHON ?= python
PIP ?= $(PYTHON) -m pip
FLASK ?= $(PYTHON) -m flask
.PHONY: help install install-dev dev lint format type-check test test-cov \
db-migrate db-upgrade db-downgrade docker-up docker-down clean
help: ## 显示全部命令帮助
@grep -E '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) | sort | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'
# ---- 依赖 ----
install: ## 安装生产依赖
$(PIP) install -r requirements.txt
install-dev: ## 安装开发依赖(含生产依赖 + 测试/规范工具)
$(PIP) install -r requirements-dev.txt
# ---- 运行 ----
dev: ## 启动开发服务器(127.0.0.1:8021)
$(PYTHON) app.py
# ---- 代码质量 ----
lint: ## 静态检查(flake8 + black + isort)
flake8 application utils extensions config src tests scripts
black --check --line-length 120 application utils extensions config src tests scripts
isort --check-only application utils extensions config src tests scripts
format: ## 自动格式化(black + isort)
black --line-length 120 application utils extensions config src tests scripts
isort application utils extensions config src tests scripts
type-check: ## 类型检查(mypy)
mypy application src
# ---- 测试 ----
test: ## 运行全部测试
pytest
test-cov: ## 带覆盖率运行测试
pytest --cov=application --cov-report=term-missing
# ---- 数据库迁移 ----
db-migrate: ## 生成迁移脚本(模型变更后)
$(FLASK) db migrate
db-upgrade: ## 应用迁移到最新
$(FLASK) db upgrade
db-downgrade: ## 回滚一次迁移
$(FLASK) db downgrade
# ---- Docker ----
docker-up: ## 启动 Docker 服务
cd docker && docker-compose up -d
docker-down: ## 停止 Docker 服务
cd docker && docker-compose down
# ---- 清理 ----
clean: ## 清理缓存与临时文件
find . -type d -name __pycache__ -not -path "*/ui/*" -exec rm -rf {} +
find . -type f -name "*.pyc" -not -path "*/ui/*" -delete
rm -rf .pytest_cache .mypy_cache
+63
View File
@@ -0,0 +1,63 @@
# ============================================================
# DjangoAdmin Flask+EleVue 项目管理配置
# ============================================================
# 说明:
# 1. 本项目为应用工程(非库),运行依赖由 requirements.txt 管理,
# 此处不重复声明(与既有部署约定保持一致)。
# 2. 本文件承载各开发工具(pytest/black/isort/mypy)统一配置。
[project]
name = "djangoadmin-flask-elevue"
version = "3.0.0"
description = "DjangoAdmin Flask+EleVue 企业数字化开发平台(后端)"
readme = "README.md"
requires-python = ">=3.9"
license = {text = "Apache-2.0"}
authors = [
{name = "一米阳光团队", email = "djangoadmin@126.com"},
]
dependencies = []
# ============================================================
# pytest
# ============================================================
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra --strict-markers"
python_files = ["test_*.py"]
filterwarnings = [
# 存量依赖(Flask-SQLAlchemy 3.x 等)DeprecationWarning 噪音较多,暂统一忽略
"ignore::DeprecationWarning",
]
# ============================================================
# black(代码格式化)
# ============================================================
[tool.black]
line-length = 120
target-version = ["py39", "py310", "py311", "py312"]
skip-string-normalization = false
# ============================================================
# isort(import 排序)
# ============================================================
[tool.isort]
profile = "black"
line_length = 120
skip_gitignore = true
known_first_party = ["application", "config", "extensions", "src", "utils"]
# ============================================================
# mypy(类型检查)
# ============================================================
[tool.mypy]
python_version = "3.9"
ignore_missing_imports = true
warn_unused_configs = true
exclude = ["migrations/", "ui/", "static/"]
# ============================================================
# coverage(pytest-cov)
# ============================================================
[tool.coverage.run]
omit = ["migrations/*", "ui/*", "static/*", "tests/*", "scripts/*"]
+19
View File
@@ -0,0 +1,19 @@
# ============================================================
# 开发环境依赖
# 安装:pip install -r requirements-dev.txt
# ============================================================
# 先引入全部生产依赖(保证开发环境与生产一致)
-r requirements.txt
# ---- 测试 ----
pytest~=8.0
pytest-cov~=6.0
# ---- 代码质量 ----
flake8~=7.0
black~=24.0
isort~=5.13
mypy~=1.14
# ---- 工程化 ----
pre-commit~=3.8
+1
View File
@@ -0,0 +1 @@
"""测试包:pytest 通过此包将项目根目录加入 sys.path。"""
+19
View File
@@ -0,0 +1,19 @@
"""
pytest 全局配置
=============================================================
P1 阶段仅搭建骨架,保证 `pytest` 可空跑。
测试 App 构建、数据库/Redis fixture 在 P8(测试完善阶段)补充:
- app fixture:create_app() + 测试库 DATABASE_URL 覆盖
- db fixture:事务内建表/清库
- client fixture:Flask test_client
"""
import os
import sys
# 项目根目录加入 sys.path(tests/ 已有 __init__.py 时 pytest 通常自动处理,
# 此处显式补充,兼容从任意目录启动 pytest 的场景)
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if ROOT not in sys.path:
sys.path.insert(0, ROOT)
+1
View File
@@ -0,0 +1 @@
"""单元测试包。"""
+1
View File
@@ -0,0 +1 @@
"""core 层单元测试。"""
+53
View File
@@ -0,0 +1,53 @@
"""
R 统一响应格式单元测试
验证 `R.ok()` / `R.failed()` 返回的 JSON 响应结构符合前后端约定:
{code, data, msg, ok}(code=0 成功,非 0 失败)。
"""
from utils import R
class TestOk:
"""成功响应"""
def test_ok_default_format(self):
resp = R.ok()
body = resp.get_json()
assert body["code"] == 0
assert body["ok"] is True
assert body["data"] is None
assert body["msg"]
def test_ok_with_data(self):
resp = R.ok(data={"id": 1, "name": "张三"})
body = resp.get_json()
assert body["code"] == 0
assert body["data"] == {"id": 1, "name": "张三"}
def test_ok_with_kwargs(self):
resp = R.ok(data=[], total=100)
body = resp.get_json()
assert body["total"] == 100
class TestFailed:
"""失败响应"""
def test_failed_default_format(self):
resp = R.failed()
body = resp.get_json()
assert body["code"] != 0
assert body["ok"] is False
assert body["data"] is None
def test_failed_custom_code(self):
resp = R.failed(msg="未授权访问", code=401)
body = resp.get_json()
assert body["code"] == 401
assert body["msg"] == "未授权访问"