chore: 部署、启动参数模板

main
NoahLan 1 week ago
parent c3d2ab56a9
commit 30f477550a

1
.gitignore vendored

@ -37,6 +37,7 @@ instance/
.env .env
.env.* .env.*
!.env.example !.env.example
!*.env.example.jinja
.envrc .envrc
# Runtime data # Runtime data

@ -27,7 +27,8 @@ description: "{{ project_name }} 业务项目 skill。用于当前由 iTi-Flask
## 项目结构 ## 项目结构
- `main.py`:导入 `config`,注册模块,创建 FastAPI app。 - `main.py`ASGI 入口,导出 `app`。
- `app/app_factory.py`:导入 `config`,注册模块,创建 FastAPI app。
- `config.py`:项目本地配置映射。 - `config.py`:项目本地配置映射。
- `app/modules/`:业务模块。 - `app/modules/`:业务模块。
- `app/models/`:项目 SQLAlchemy 模型。 - `app/models/`:项目 SQLAlchemy 模型。
@ -35,6 +36,12 @@ description: "{{ project_name }} 业务项目 skill。用于当前由 iTi-Flask
- `tests/`pytest 路由和行为测试。 - `tests/`pytest 路由和行为测试。
- `app.sh`Linux/macOS/Git Bash 命令入口。 - `app.sh`Linux/macOS/Git Bash 命令入口。
- `app.cmd`Windows CMD 命令入口。 - `app.cmd`Windows CMD 命令入口。
- `Dockerfile`:容器镜像构建入口。
- `docker-compose.yml`:本地 Compose 部署入口。
- `docker-compose.with-db.yml`:本地 MySQL 叠加部署入口。
- `.env.example`:本地和 Compose 环境变量样例。
- `.vscode/launch.json`VSCode FastAPI 调试配置。
- `.dockerignore`Docker 构建排除规则。
- `pyproject.toml`:包信息和依赖。 - `pyproject.toml`:包信息和依赖。
- `.copier-answers.yml`Copier 模板更新锚点。 - `.copier-answers.yml`Copier 模板更新锚点。
@ -68,6 +75,12 @@ description: "{{ project_name }} 业务项目 skill。用于当前由 iTi-Flask
- 同步模板骨架:`./app.sh template-update` - 同步模板骨架:`./app.sh template-update`
- 运行测试:`./app.sh test` - 运行测试:`./app.sh test`
- 本地启动:`./app.sh serve 8000` - 本地启动:`./app.sh serve 8000`
- 构建 Docker 镜像:`./app.sh docker-build`
- 启动 Docker Compose`./app.sh docker-up`
- 启动 Docker Compose 和 MySQL`./app.sh docker-up-db`
- 停止 Docker Compose`./app.sh docker-down`
- 停止 Docker Compose 和 MySQL`./app.sh docker-down-db`
- 查看应用容器日志:`./app.sh docker-logs`
- 创建 migration`./app.sh migration "alice add order table"` - 创建 migration`./app.sh migration "alice add order table"`
- 执行 migration`./app.sh migrate` - 执行 migration`./app.sh migrate`
- 查看 Alembic heads`./app.sh heads` - 查看 Alembic heads`./app.sh heads`

@ -0,0 +1,19 @@
.git
.gitignore
.venv
__pycache__
*.py[cod]
.pytest_cache
.mypy_cache
.ruff_cache
*.egg-info
build
dist
runtime
logs
.env
.env.*
!.env.example
*.db
*.sqlite
*.sqlite3

@ -0,0 +1,13 @@
APP_ENV=prod
APP_PORT=8000
MYSQL_HOST=host.docker.internal
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=root-password
MYSQL_DATABASE=app
MYSQL_USER=app
MYSQL_PASSWORD=change-me
SECRET_KEY=change-me
JWT_SECRET_KEY=change-me
LOG_FILE_ENABLED=true

@ -0,0 +1,26 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "{{ project_name }}: FastAPI",
"type": "debugpy",
"request": "launch",
"module": "uvicorn",
"args": [
"main:app",
"--reload",
"--host",
"127.0.0.1",
"--port",
"8000"
],
"cwd": "${workspaceFolder}",
"env": {
"APP_ENV": "dev",
"APP_ENV_DIR": "${workspaceFolder}"
},
"jinja": true,
"justMyCode": true
}
]
}

@ -0,0 +1,24 @@
FROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
UV_LINK_MODE=copy \
UV_COMPILE_BYTECODE=1
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl git \
&& rm -rf /var/lib/apt/lists/*
COPY . .
RUN pip install --no-cache-dir uv \
&& if [ -f uv.lock ]; then \
uv sync --frozen --no-dev; \
else \
uv sync --no-dev; \
fi
EXPOSE 8000
CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

@ -21,6 +21,7 @@ AI 修改本项目时优先读:
## 初始化 ## 初始化
```bash ```bash
cp .env.example .env
./app.sh init ./app.sh init
``` ```
@ -59,6 +60,26 @@ app.cmd init-system
APP_ENV=prod ./app.sh migrate APP_ENV=prod ./app.sh migrate
``` ```
## Docker
```bash
cp .env.example .env
./app.sh docker-up
./app.sh docker-logs
./app.sh docker-down
```
`docker-compose.yml` 默认只启动应用,数据库使用外部 MySQL。
需要本地 MySQL 时使用:
```bash
./app.sh docker-up-db
./app.sh docker-down-db
```
应用容器启动时会先执行 migration{% if include_system %} 和系统 seed{% endif %}。
本地运行数据写入 `runtime/`MySQL 数据写入 Compose volume。
## 同步更新 ## 同步更新
同步框架包: 同步框架包:
@ -79,10 +100,15 @@ APP_ENV=prod ./app.sh migrate
./app.sh template-update ./app.sh template-update
``` ```
模板更新会改 `main.py`、`config.py`、`app.sh`、`app.cmd`、示例模块、测试和项目 skill 等模板拥有的文件。 模板更新会改 `main.py`、`app/app_factory.py`、`config.py`、`Dockerfile`、`docker-compose.yml`、`docker-compose.with-db.yml`、`.dockerignore`、`.env.example`、`.vscode/launch.json`、`app.sh`、`app.cmd`、示例模块、测试和项目 skill 等模板拥有的文件。
该命令跟随模板仓库 `HEAD`。 该命令跟随模板仓库 `HEAD`。
执行前先提交或暂存当前项目改动,执行后检查 diff。 执行前先提交或暂存当前项目改动,执行后检查 diff。
## VSCode 调试
模板内置 `.vscode/launch.json`。
选择 `{{ project_name }}: FastAPI` 即可用 `uvicorn main:app --reload` 调试。
## 数据库迁移 ## 数据库迁移
```bash ```bash

@ -36,6 +36,12 @@ if "%COMMAND%"=="template-update" (
) )
if "%COMMAND%"=="test" goto test if "%COMMAND%"=="test" goto test
if "%COMMAND%"=="serve" goto serve if "%COMMAND%"=="serve" goto serve
if "%COMMAND%"=="docker-build" goto docker_build
if "%COMMAND%"=="docker-up" goto docker_up
if "%COMMAND%"=="docker-up-db" goto docker_up_db
if "%COMMAND%"=="docker-down" goto docker_down
if "%COMMAND%"=="docker-down-db" goto docker_down_db
if "%COMMAND%"=="docker-logs" goto docker_logs
if "%COMMAND%"=="migrate" goto migrate if "%COMMAND%"=="migrate" goto migrate
if "%COMMAND%"=="migration" goto migration if "%COMMAND%"=="migration" goto migration
if "%COMMAND%"=="heads" goto heads if "%COMMAND%"=="heads" goto heads
@ -63,6 +69,12 @@ echo template-check 检查 Copier 模板是否有更新
echo template-update 按 Copier 模板更新项目骨架 echo template-update 按 Copier 模板更新项目骨架
echo test 运行测试uv run --extra dev pytest -q echo test 运行测试uv run --extra dev pytest -q
echo serve [环境] [端口] 本地启动,默认 dev / 8000 echo serve [环境] [端口] 本地启动,默认 dev / 8000
echo docker-build 构建 Docker 镜像
echo docker-up 启动 Docker Compose 应用
echo docker-up-db 启动 Docker Compose 应用和 MySQL
echo docker-down 停止 Docker Compose 应用
echo docker-down-db 停止 Docker Compose 应用和 MySQL
echo docker-logs 查看应用容器日志
echo migrate 执行 Alembic upgrade head echo migrate 执行 Alembic upgrade head
echo migration ^<说明^> 生成 migration说明建议以作者名开头 echo migration ^<说明^> 生成 migration说明建议以作者名开头
echo heads 查看 Alembic heads echo heads 查看 Alembic heads
@ -75,6 +87,7 @@ echo.
echo 示例: echo 示例:
echo app.cmd install echo app.cmd install
echo app.cmd serve echo app.cmd serve
echo app.cmd docker-up
echo app.cmd serve test 8000 echo app.cmd serve test 8000
echo set APP_ENV=prod ^&^& app.cmd migrate echo set APP_ENV=prod ^&^& app.cmd migrate
echo app.cmd migration "alice add order table" echo app.cmd migration "alice add order table"
@ -100,7 +113,6 @@ goto end
:serve :serve
set "ENV_NAME=%APP_ENV%" set "ENV_NAME=%APP_ENV%"
if not defined ENV_NAME set "ENV_NAME=%ITI_ENV%"
if not defined ENV_NAME set "ENV_NAME=dev" if not defined ENV_NAME set "ENV_NAME=dev"
set "PORT=8000" set "PORT=8000"
set "ARG1=%~1" set "ARG1=%~1"
@ -127,10 +139,33 @@ if /I "%ARG1%"=="dev" (
) )
if "%PORT%"=="" set "PORT=8000" if "%PORT%"=="" set "PORT=8000"
set "APP_ENV=%ENV_NAME%" set "APP_ENV=%ENV_NAME%"
set "ITI_ENV=%ENV_NAME%"
uv run uvicorn main:app --reload --port "%PORT%" uv run uvicorn main:app --reload --port "%PORT%"
goto end goto end
:docker_build
docker compose build
goto end
:docker_up
docker compose up -d --build
goto end
:docker_up_db
docker compose -f docker-compose.yml -f docker-compose.with-db.yml up -d --build
goto end
:docker_down
docker compose down
goto end
:docker_down_db
docker compose -f docker-compose.yml -f docker-compose.with-db.yml down
goto end
:docker_logs
docker compose logs -f app
goto end
:migrate :migrate
uv run alembic -c migrations/alembic.ini upgrade head uv run alembic -c migrations/alembic.ini upgrade head
goto end goto end
@ -157,7 +192,8 @@ uv run iti-system migrations sync --target migrations/versions
goto end goto end
:system_seed :system_seed
uv run iti-system seed system main:app set "PYTHONPATH=."
uv run iti-system seed system app:create_app
goto end goto end
:init_system :init_system
@ -165,7 +201,8 @@ uv run iti-system migrations sync --target migrations/versions
if errorlevel 1 goto end if errorlevel 1 goto end
uv run alembic -c migrations/alembic.ini upgrade head uv run alembic -c migrations/alembic.ini upgrade head
if errorlevel 1 goto end if errorlevel 1 goto end
uv run iti-system seed system main:app set "PYTHONPATH=."
uv run iti-system seed system app:create_app
goto end goto end
{% endif %}:init {% endif %}:init
@ -175,7 +212,8 @@ if errorlevel 1 goto end
if errorlevel 1 goto end if errorlevel 1 goto end
{% endif %}uv run alembic -c migrations/alembic.ini upgrade head {% endif %}uv run alembic -c migrations/alembic.ini upgrade head
if errorlevel 1 goto end if errorlevel 1 goto end
{% if include_system %}uv run iti-system seed system main:app {% if include_system %}set "PYTHONPATH=."
uv run iti-system seed system app:create_app
{% endif %}goto end {% endif %}goto end
:end :end

@ -29,6 +29,12 @@ show_help() {
template-update 按 Copier 模板更新项目骨架 template-update 按 Copier 模板更新项目骨架
test 运行测试uv run --extra dev pytest -q test 运行测试uv run --extra dev pytest -q
serve [环境] [端口] 本地启动,默认 dev / 8000 serve [环境] [端口] 本地启动,默认 dev / 8000
docker-build 构建 Docker 镜像
docker-up 启动 Docker Compose 应用
docker-up-db 启动 Docker Compose 应用和 MySQL
docker-down 停止 Docker Compose 应用
docker-down-db 停止 Docker Compose 应用和 MySQL
docker-logs 查看应用容器日志
migrate 执行 Alembic upgrade head migrate 执行 Alembic upgrade head
migration <说明> 生成 migration说明建议以作者名开头 migration <说明> 生成 migration说明建议以作者名开头
heads 查看 Alembic heads heads 查看 Alembic heads
@ -41,6 +47,7 @@ show_help() {
示例: 示例:
./app.sh install ./app.sh install
./app.sh serve ./app.sh serve
./app.sh docker-up
./app.sh serve test 8000 ./app.sh serve test 8000
APP_ENV=prod ./app.sh migrate APP_ENV=prod ./app.sh migrate
./app.sh migration "alice add order table" ./app.sh migration "alice add order table"
@ -72,7 +79,7 @@ case "$command" in
uv run --extra dev pytest -q uv run --extra dev pytest -q
;; ;;
serve) serve)
env_name=${APP_ENV:-${ITI_ENV:-dev}} env_name=${APP_ENV:-dev}
port=8000 port=8000
if [ $# -gt 0 ]; then if [ $# -gt 0 ]; then
case "$1" in case "$1" in
@ -92,7 +99,25 @@ case "$command" in
if [ "$env_name" = default ]; then if [ "$env_name" = default ]; then
env_name=dev env_name=dev
fi fi
APP_ENV="$env_name" ITI_ENV="$env_name" uv run uvicorn main:app --reload --port "$port" APP_ENV="$env_name" uv run uvicorn main:app --reload --port "$port"
;;
docker-build)
docker compose build
;;
docker-up)
docker compose up -d --build
;;
docker-up-db)
docker compose -f docker-compose.yml -f docker-compose.with-db.yml up -d --build
;;
docker-down)
docker compose down
;;
docker-down-db)
docker compose -f docker-compose.yml -f docker-compose.with-db.yml down
;;
docker-logs)
docker compose logs -f app
;; ;;
migrate) migrate)
uv run alembic -c migrations/alembic.ini upgrade head uv run alembic -c migrations/alembic.ini upgrade head
@ -115,18 +140,18 @@ case "$command" in
uv run iti-system migrations sync --target migrations/versions uv run iti-system migrations sync --target migrations/versions
;; ;;
system-seed) system-seed)
uv run iti-system seed system main:app PYTHONPATH=. uv run iti-system seed system app:create_app
;; ;;
init-system) init-system)
uv run iti-system migrations sync --target migrations/versions uv run iti-system migrations sync --target migrations/versions
uv run alembic -c migrations/alembic.ini upgrade head uv run alembic -c migrations/alembic.ini upgrade head
uv run iti-system seed system main:app PYTHONPATH=. uv run iti-system seed system app:create_app
;; ;;
{% endif %} init) {% endif %} init)
uv sync --extra dev uv sync --extra dev
{% if include_system %} uv run iti-system migrations sync --target migrations/versions {% if include_system %} uv run iti-system migrations sync --target migrations/versions
{% endif %} uv run alembic -c migrations/alembic.ini upgrade head {% endif %} uv run alembic -c migrations/alembic.ini upgrade head
{% if include_system %} uv run iti-system seed system main:app {% if include_system %} PYTHONPATH=. uv run iti-system seed system app:create_app
{% endif %} ;; {% endif %} ;;
*) *)
echo "未知命令:$command" >&2 echo "未知命令:$command" >&2

@ -1 +1,3 @@
"""{{ project_name }} package.""" from .app_factory import create_app
__all__ = ["create_app"]

@ -0,0 +1,27 @@
from __future__ import annotations
import os
from iti import create_app as create_framework_app
{% if include_system %}
from iti_system import create_system_module
{% endif %}
from config import config
from app.modules.example.module import ExampleModule
def create_app(config_name: str | None = None, config_overrides: dict | None = None):
config_name = config_name or os.getenv("APP_ENV", "dev")
modules = [ExampleModule()]
{% if include_system %}
modules.append(create_system_module())
{% endif %}
app = create_framework_app(
config_name=config_name,
config_mapping=config,
modules=modules,
)
if config_overrides:
for key, value in config_overrides.items():
setattr(app.state.config, key.lower(), value)
return app

@ -1,55 +1,45 @@
import os from __future__ import annotations
from pathlib import Path from pathlib import Path
from iti.config import ( from iti.config import (
BaseConfig,
DevConfig as BaseDevConfig, DevConfig as BaseDevConfig,
TestConfig as BaseTestConfig,
ProdConfig as BaseProdConfig, ProdConfig as BaseProdConfig,
) )
BASE_DIR = Path(__file__).resolve().parent BASE_DIR = Path(__file__).resolve().parent
APP_NAME = "{{ project_name }}"
def runtime_path(name: str) -> str:
return str(BASE_DIR / "runtime" / name)
def apply_project_config(config) -> None:
config.app_name = APP_NAME
config.base_dir = BASE_DIR
config.file_storage["LOCAL"]["base_path"] = runtime_path("uploads")
config.log_dir = runtime_path("logs")
class DevConfig(BaseDevConfig): class DevConfig(BaseDevConfig):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self.app_name = "{{ project_name }}" apply_project_config(self)
self.base_dir = BASE_DIR
self.file_storage["LOCAL"]["base_path"] = str(BASE_DIR / "runtime" / "uploads")
self.log_dir = str(BASE_DIR / "runtime" / "logs")
class TestConfig(BaseConfig): class TestConfig(BaseTestConfig):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__( super().__init__()
app_name="{{ project_name }}", apply_project_config(self)
app_env="test",
testing=True,
database_url=os.getenv(
"DATABASE_URL",
f"mysql+pymysql://{os.getenv('MYSQL_USER', 'root')}:"
f"{os.getenv('MYSQL_PASSWORD', 'password')}@"
f"{os.getenv('MYSQL_HOST', '127.0.0.1')}:"
f"{os.getenv('MYSQL_PORT', '3306')}/"
f"{os.getenv('MYSQL_DATABASE', 'app_test')}?charset=utf8mb4",
),
base_dir=BASE_DIR,
ratelimit_enabled=False,
log_file_enabled=False,
)
self.app_name = "{{ project_name }}"
self.base_dir = BASE_DIR
self.log_dir = str(BASE_DIR / "runtime" / "logs")
class ProdConfig(BaseProdConfig): class ProdConfig(BaseProdConfig):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self.app_name = "{{ project_name }}" apply_project_config(self)
self.base_dir = BASE_DIR
self.log_dir = str(BASE_DIR / "runtime" / "logs")
config = { config = {

@ -0,0 +1,31 @@
services:
app:
environment:
MYSQL_HOST: db
MYSQL_PORT: 3306
depends_on:
db:
condition: service_healthy
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root-password}
MYSQL_DATABASE: ${MYSQL_DATABASE:-app}
MYSQL_USER: ${MYSQL_USER:-app}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-change-me}
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
ports:
- "${MYSQL_PORT:-3306}:3306"
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -u$${MYSQL_USER} -p$${MYSQL_PASSWORD} --silent"]
interval: 10s
timeout: 5s
retries: 10
volumes:
mysql-data:

@ -0,0 +1,26 @@
services:
app:
build:
context: .
image: {{ project_slug | replace('_', '-') }}:local
environment:
APP_ENV: ${APP_ENV:-prod}
SECRET_KEY: ${SECRET_KEY:-change-me}
JWT_SECRET_KEY: ${JWT_SECRET_KEY:-change-me}
MYSQL_HOST: ${MYSQL_HOST:-host.docker.internal}
MYSQL_PORT: ${MYSQL_PORT:-3306}
MYSQL_DATABASE: ${MYSQL_DATABASE:-app}
MYSQL_USER: ${MYSQL_USER:-app}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-change-me}
LOG_FILE_ENABLED: ${LOG_FILE_ENABLED:-true}
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- "${APP_PORT:-8000}:8000"
volumes:
- ./runtime:/app/runtime
command: >
sh -c "{% if include_system %}uv run iti-system migrations sync --target migrations/versions &&
{% endif %}uv run alembic -c migrations/alembic.ini upgrade head &&
{% if include_system %}PYTHONPATH=. uv run iti-system seed system app:create_app &&
{% endif %}uv run uvicorn main:app --host 0.0.0.0 --port 8000"

@ -1,22 +1,4 @@
import os from app import create_app
from iti import create_app
{% if include_system %}from iti_system import create_system_module
{% endif -%} app = create_app()
from config import config
from app.modules.example.module import ExampleModule
modules = [
ExampleModule(),
{% if include_system %} create_system_module(),
{% endif -%}
]
app = create_app(
config_name=os.getenv("APP_ENV", os.getenv("ITI_ENV", "dev")),
config_mapping=config,
modules=modules,
)

@ -30,7 +30,7 @@ target_metadata = Base.metadata
def get_url() -> str: def get_url() -> str:
env_name = os.getenv("APP_ENV", os.getenv("ITI_ENV", "dev")) env_name = os.getenv("APP_ENV", "dev")
config_cls = app_config.get(env_name, app_config["default"]) config_cls = app_config.get(env_name, app_config["default"])
return os.getenv("DATABASE_URL") or config_cls().database_url return os.getenv("DATABASE_URL") or config_cls().database_url

@ -6,8 +6,8 @@
## 环境选择 ## 环境选择
```bash ```bash
ITI_ENV=dev uv run uvicorn main:app --reload APP_ENV=dev uv run uvicorn main:app --reload
ITI_ENV=prod uv run uvicorn main:app APP_ENV=prod uv run uvicorn main:app
``` ```
也可以显式传入: 也可以显式传入:
@ -47,10 +47,10 @@ MYSQL_DATABASE=iti_dev
框架会从当前目录加载第一个存在的文件: 框架会从当前目录加载第一个存在的文件:
1. `.env.local` 1. `.env.local`
2. `.env.<ITI_ENV>` 2. `.env.<APP_ENV>`
3. `.env` 3. `.env`
`ITI_ENV_DIR` 可指定查找目录。 `APP_ENV_DIR` 可指定查找目录。
## 常用字段 ## 常用字段

@ -25,6 +25,7 @@ copier-template/.codex/skills/{{ project_slug | lower | replace('_', '-') }}-pro
```bash ```bash
./iti.sh make-app ../my-business-app my-business-app ./iti.sh make-app ../my-business-app my-business-app
cd ../my-business-app cd ../my-business-app
cp .env.example .env
./app.sh init ./app.sh init
./app.sh serve 8000 ./app.sh serve 8000
``` ```
@ -56,9 +57,16 @@ app.cmd serve 8000
## 生成内容 ## 生成内容
- `main.py` - `main.py`
- `app/app_factory.py`
- `config.py` - `config.py`
- `pyproject.toml` - `pyproject.toml`
- `migrations/` - `migrations/`
- `Dockerfile`
- `docker-compose.yml`
- `docker-compose.with-db.yml`
- `.dockerignore`
- `.vscode/launch.json`
- `.env.example`
- 示例 FastAPI 模块 - 示例 FastAPI 模块
- 示例 SQLAlchemy 模型 - 示例 SQLAlchemy 模型
- 示例测试 - 示例测试
@ -94,6 +102,11 @@ app.cmd init-system
./app.sh install ./app.sh install
./app.sh test ./app.sh test
./app.sh serve 8000 ./app.sh serve 8000
./app.sh docker-up
./app.sh docker-up-db
./app.sh docker-logs
./app.sh docker-down
./app.sh docker-down-db
./app.sh migration "alice add order table" ./app.sh migration "alice add order table"
./app.sh migrate ./app.sh migrate
``` ```
@ -138,5 +151,23 @@ app.cmd init-system
模板更新前,业务项目工作区必须干净。 模板更新前,业务项目工作区必须干净。
执行后检查 diff再运行测试。 执行后检查 diff再运行测试。
模板拥有的文件包括 `main.py`、`config.py`、`app.sh`、`app.cmd`、`pyproject.toml`、`migrations/`、示例模块、示例测试、README 和项目 skill。 模板拥有的文件包括 `main.py`、`app/app_factory.py`、`config.py`、`Dockerfile`、`docker-compose.yml`、`docker-compose.with-db.yml`、`.dockerignore`、`.env.example`、`.vscode/launch.json`、`app.sh`、`app.cmd`、`pyproject.toml`、`migrations/`、示例模块、示例测试、README 和项目 skill。
业务项目自己的模块、模型、API 文档和业务 README 由业务项目维护。 业务项目自己的模块、模型、API 文档和业务 README 由业务项目维护。
## Docker
模板生成:
- `Dockerfile`:基于 `python:3.11-slim`,使用 `uv sync --frozen --no-dev` 安装运行依赖。
- `docker-compose.yml`:启动应用,默认连接外部 MySQL。
- `docker-compose.with-db.yml`:叠加启动 MySQL 8.4。
- `.env.example`Compose 和本地运行共用的环境变量样例。
- `.dockerignore`:排除虚拟环境、运行数据和本地密钥。
应用容器启动时会执行业务 migration。
`iti-system` 的项目还会先同步 system migration并执行 system seed。
## VSCode
模板生成 `.vscode/launch.json`
默认调试配置以 `uvicorn main:app --reload` 启动,使用 `APP_ENV=dev`

@ -16,7 +16,7 @@
系统包提供: 系统包提供:
```bash ```bash
uv run iti-system seed system main:app PYTHONPATH=. uv run iti-system seed system app:create_app
``` ```
它会写入默认角色、管理员、系统菜单、字典和配置。 它会写入默认角色、管理员、系统菜单、字典和配置。

@ -33,7 +33,7 @@ CREATE DATABASE my_business_app_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unico
export DATABASE_URL='mysql+pymysql://root:password@127.0.0.1:3306/iti_test?charset=utf8mb4' export DATABASE_URL='mysql+pymysql://root:password@127.0.0.1:3306/iti_test?charset=utf8mb4'
uv run alembic upgrade head uv run alembic upgrade head
uv run iti-system migrations sync --target migrations/versions uv run iti-system migrations sync --target migrations/versions
uv run iti-system seed system main:app PYTHONPATH=. uv run iti-system seed system app:create_app
uv run uvicorn main:app --reload uv run uvicorn main:app --reload
``` ```
@ -48,7 +48,23 @@ curl http://127.0.0.1:8000/ready
## Docker Compose 健康检查 ## Docker Compose 健康检查
服务容器可使用: 模板生成项目已经包含 Docker Compose
```bash
cp .env.example .env
./app.sh docker-up
./app.sh docker-logs
./app.sh docker-down
```
如需本地 MySQL
```bash
./app.sh docker-up-db
./app.sh docker-down-db
```
服务容器使用:
```yaml ```yaml
healthcheck: healthcheck:

@ -36,7 +36,7 @@ from sqlalchemy.exc import SQLAlchemyError
from iti.auth.permissions import StaticPermissionProvider from iti.auth.permissions import StaticPermissionProvider
from iti.audit import init_audit from iti.audit import init_audit
from iti.cache import CacheManager from iti.cache import CacheManager
from iti.config import BaseConfig, get_config from iti.config import BaseConfig, get_config, get_env_name
from iti.db import configure_db from iti.db import configure_db
from iti.exceptions import ItiError from iti.exceptions import ItiError
from iti.health import router as health_router from iti.health import router as health_router
@ -282,7 +282,7 @@ def _resolve_config(
if config_mapping is None: if config_mapping is None:
return get_config(config_name) return get_config(config_name)
if isinstance(config_mapping, Mapping): if isinstance(config_mapping, Mapping):
env_name = config_name or "dev" env_name = config_name or get_env_name()
value = config_mapping.get(env_name, config_mapping.get("default")) value = config_mapping.get(env_name, config_mapping.get("default"))
if value is None: if value is None:
return get_config(config_name) return get_config(config_name)

@ -11,9 +11,13 @@ from dotenv import load_dotenv
BASE_DIR = Path(os.getenv("ITI_BASE_DIR", Path.cwd())).resolve() BASE_DIR = Path(os.getenv("ITI_BASE_DIR", Path.cwd())).resolve()
def get_env_name(default: str = "dev") -> str:
return os.getenv("APP_ENV") or default
def load_env_file(env_dir: str | os.PathLike | None = None) -> bool: def load_env_file(env_dir: str | os.PathLike | None = None) -> bool:
search_dir = Path(env_dir or os.getenv("ITI_ENV_DIR") or Path.cwd()).resolve() search_dir = Path(env_dir or os.getenv("APP_ENV_DIR") or Path.cwd()).resolve()
env_name = os.getenv("APP_ENV", os.getenv("ITI_ENV", "dev")) env_name = get_env_name()
for name in (".env.local", f".env.{env_name}", ".env"): for name in (".env.local", f".env.{env_name}", ".env"):
path = search_dir / name path = search_dir / name
if path.exists(): if path.exists():
@ -176,6 +180,6 @@ config = {
def get_config(env_name: str | None = None) -> BaseConfig: def get_config(env_name: str | None = None) -> BaseConfig:
env_name = env_name or os.getenv("APP_ENV", os.getenv("ITI_ENV", "dev")) env_name = env_name or get_env_name()
config_cls = config.get(env_name, config["default"]) config_cls = config.get(env_name, config["default"])
return config_cls() return config_cls()

Loading…
Cancel
Save