尝试提交

This commit is contained in:
李宇奇 2025-06-04 10:39:32 +08:00
commit e8b2a9a99c
33 changed files with 1212 additions and 0 deletions

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,12 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="pathlib.Path.__add__" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

4
.idea/misc.xml Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (wms-py)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/wms-py.iml" filepath="$PROJECT_DIR$/.idea/wms-py.iml" />
</modules>
</component>
</project>

10
.idea/wms-py.iml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

258
README.md Normal file
View File

@ -0,0 +1,258 @@
# WMS FastAPI 项目
这是一个基于 FastAPI 的仓库管理系统(WMS),采用标准的项目结构,支持多环境配置。
## 项目结构
```
wms-py/
├── app/ # 应用核心代码
│ ├── __init__.py
│ ├── main.py # 主应用文件
│ ├── config/ # 配置模块
│ │ ├── __init__.py
│ │ └── settings.py # 配置管理
│ ├── controllers/ # 控制器(路由)
│ │ ├── __init__.py
│ │ ├── base_controller.py # 基础接口
│ │ ├── database_controller.py # 数据库测试接口
│ │ └── location_controller.py # 库位管理接口 🆕
│ ├── services/ # 业务服务层 🆕
│ │ ├── __init__.py
│ │ └── location_service.py # 库位业务逻辑
│ ├── models/ # 数据模型 🆕
│ │ ├── __init__.py
│ │ └── location.py # 库位数据模型
│ ├── schemas/ # Pydantic模式 🆕
│ │ ├── __init__.py
│ │ ├── common.py # 通用响应模式
│ │ └── location.py # 库位请求/响应模式
│ └── utils/ # 工具类
│ ├── __init__.py
│ ├── database.py # 数据库工具类
│ └── string_utils.py # 字符串工具 🆕
├── config/ # 环境配置文件
│ ├── development.yaml # 开发环境配置
│ └── production.yaml # 生产环境配置
├── requirements.txt # 依赖包列表
├── run.py # 启动脚本
├── create_tables.py # 数据库表创建脚本 🆕
└── README.md # 项目说明
```
## 功能特性
- ✅ 多环境配置支持 (开发/生产)
- ✅ YAML 配置文件
- ✅ 数据库连接管理
- ✅ 标准项目结构
- ✅ API 文档自动生成
- ✅ 数据库测试接口
- ✅ 健康检查接口
- ✅ **库位管理模块** 🆕
- 批量生成库位
- 查询已使用库位数量
- 库位信息查询
- 库位统计功能
## 快速开始
### 1. 安装依赖
```bash
pip install -r requirements.txt
```
### 2. 配置数据库
根据需要修改配置文件:
**开发环境** (`config/development.yaml`):
```yaml
database:
host: "localhost"
port: 3306
username: "root"
password: "root"
database: "wms_ntyc"
```
### 3. 创建数据库表
```bash
python create_tables.py
```
### 4. 运行应用
```bash
# 开发环境
python run.py
# 生产环境
set ENVIRONMENT=production && python run.py
```
## API接口
### 基础接口
- `GET /` - 系统信息
- `GET /health` - 健康检查
- `GET /config` - 配置信息
### 数据库测试接口
- `GET /database/test` - 测试数据库连接
- `GET /database/tables` - 获取所有表名
- `GET /database/query?sql=SELECT * FROM table_name` - 执行SQL查询
- `GET /database/table/{table_name}` - 查看指定表数据
### 库位管理接口 🆕
**库位生成**:
- `POST /wms/location/genLocations` - 批量生成库位
请求示例:
```json
{
"l_row": 5,
"l_col": 8,
"l_layer": 6,
"l_depth": 2,
"sub_area": "A",
"area_id": 1
}
```
**库位查询**:
- `GET /wms/location/getUsedLocations?equipment_id=1&location_type=1` - 获取已使用库位数量
- `GET /wms/location/location/{location_id}` - 根据ID查询库位信息
- `GET /wms/location/locations/area/{area_id}` - 根据区域查询库位列表
- `GET /wms/location/locations/stats` - 获取库位统计信息
### API 文档
- `GET /docs` - Swagger UI 文档
- `GET /redoc` - ReDoc 文档
## 库位管理功能详解
### 1. 库位生成逻辑
库位ID格式`{子区域}{行号02d}-{列号02d}-{层号02d}-{深度02d}`
例如:
- 子区域: A
- 行: 1, 列: 2, 层: 3, 深度: 1
- 生成ID: `A01-02-03-01`
### 2. 数据库表结构
```sql
CREATE TABLE t_app_location (
location_id VARCHAR(50) PRIMARY KEY COMMENT '库位ID',
location_name VARCHAR(100) DEFAULT '' COMMENT '库位名称',
location_type INT DEFAULT 1 COMMENT '库位类型',
is_occupy INT DEFAULT 0 COMMENT '是否占用 0-未占用 1-已占用',
is_enable INT DEFAULT 0 COMMENT '是否启用',
equipment_id INT DEFAULT 0 COMMENT '设备ID',
aisle_num_left INT COMMENT '左侧巷道号',
aisle_num_right INT COMMENT '右侧巷道号',
l_row INT COMMENT '行',
l_col INT COMMENT '列',
l_layer INT COMMENT '层',
l_depth INT COMMENT '深度',
remark TEXT DEFAULT '' COMMENT '备注',
area_id INT DEFAULT 1 COMMENT '区域ID',
sub_area VARCHAR(50) DEFAULT '' COMMENT '子区域'
);
```
### 3. 使用示例
**生成库位**
```bash
curl -X POST "http://localhost:12315/wms/location/genLocations" \
-H "Content-Type: application/json" \
-d '{
"l_row": 2,
"l_col": 3,
"l_layer": 4,
"l_depth": 1,
"sub_area": "A",
"area_id": 1
}'
```
**查询统计**
```bash
curl "http://localhost:12315/wms/location/locations/stats"
```
## 项目架构说明
### 分层架构
1. **Controller层** (`app/controllers/`)
- 处理HTTP请求
- 参数验证
- 路由定义
2. **Service层** (`app/services/`)
- 业务逻辑处理
- 事务管理
- 数据操作
3. **Model层** (`app/models/`)
- 数据库表映射
- ORM模型定义
4. **Schema层** (`app/schemas/`)
- 请求/响应数据验证
- API文档生成
### 依赖注入
项目使用FastAPI的依赖注入系统
- 数据库会话注入
- 服务类注入
- 配置注入
### 对应关系
| Java概念 | Python/FastAPI概念 | 文件位置 |
|---------|-------------------|----------|
| @RestController | APIRouter | `app/controllers/` |
| @Service | Service类 | `app/services/` |
| @Entity | SQLAlchemy Model | `app/models/` |
| DTO | Pydantic Schema | `app/schemas/` |
| @Autowired | Depends() | 依赖注入 |
## IDE 配置
### PyCharm 配置
创建运行配置:
- **Name**: `WMS Development`
- **Script path**: `run.py`
- **Environment variables**: `ENVIRONMENT=development`
## 注意事项
1. 确保数据库服务已启动
2. 首次运行前执行 `python create_tables.py` 创建表
3. 修改配置文件后需要重启应用
4. 库位生成前请确认参数,避免重复数据
## 故障排除
### 常见问题
1. **表不存在**
- 运行 `python create_tables.py` 创建表
2. **库位ID重复**
- 检查参数是否与已有数据冲突
- 清理测试数据后重新生成
3. **数据库连接失败**
- 检查配置文件中的数据库信息
- 确认数据库服务状态

3
app/__init__.py Normal file
View File

@ -0,0 +1,3 @@
from .config import settings
# WMS FastAPI 应用包

85
app/config.py Normal file
View File

@ -0,0 +1,85 @@
import os
from pathlib import Path
import yaml
from pydantic import BaseModel, computed_field, Field
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
class DatabaseConfig(BaseModel):
driver: str = "mysql+pymysql"
host: str
port: int = 3306
name: str
username: str
password: str
params: str = "characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&rewriteBatchedStatements=true"
@computed_field
@property
def url(self) -> str:
"""生成数据库连接 URL"""
return f"{self.driver}://{self.username}:{self.password}@{self.host}:{self.port}/{self.name}?{self.params}"
class ServerConfig(BaseModel):
port: int
context_path: str = "/"
class AppConfig(BaseModel):
name: str = "wms_main"
max_file_size: int = 100 * 1024 * 1024 # 100MB
max_request_size: int = 1000 * 1024 * 1024 # 1000MB
class Settings(BaseModel):
"""全局配置模型"""
app: AppConfig
server: ServerConfig
database: DatabaseConfig
env: str = Field(default="development") # 当前环境名称
@classmethod
def from_env(cls):
"""从环境变量加载配置"""
env_name = os.getenv("ENV", "development")
# 获取项目根目录
base_dir = Path(__file__).resolve().parent.parent
# 加载基础配置
base_path = base_dir / "config" / "base.yaml"
base_data = {}
if base_path.exists():
with open(base_path, "r") as f:
base_data = yaml.safe_load(f) or {}
# 加载环境特定配置
env_path = base_dir / "config" / f"{env_name}.yaml"
env_data = {}
if env_path.exists():
with open(env_path, "r") as f:
env_data = yaml.safe_load(f) or {}
# 深度合并配置
def deep_merge(base: dict, update: dict) -> dict:
"""递归合并两个字典"""
for key, value in update.items():
if isinstance(value, dict) and key in base and isinstance(base[key], dict):
base[key] = deep_merge(base[key], value)
else:
base[key] = value
return base
# 合并配置
merged_config = deep_merge(base_data, env_data)
merged_config["env"] = env_name # 添加环境名称
return cls(**merged_config)
# 创建全局配置对象
settings = Settings.from_env()

1
app/config/__init__.py Normal file
View File

@ -0,0 +1 @@
# 配置包

71
app/config/settings.py Normal file
View File

@ -0,0 +1,71 @@
import os
import yaml
from typing import Dict, Any
from pydantic import BaseModel
class DatabaseConfig(BaseModel):
"""数据库配置"""
host: str
port: int = 3306
username: str
password: str
database: str
charset: str = "utf8mb4"
class AppConfig(BaseModel):
"""应用配置"""
name: str
debug: bool = False
host: str = "localhost"
port: int = 12315
database: DatabaseConfig
class Settings:
"""配置管理器"""
def __init__(self):
self._config: AppConfig = None
self._env = os.getenv("ENVIRONMENT", "development")
self.load_config()
def load_config(self):
"""加载配置文件"""
config_file = f"config/{self._env}.yaml"
if not os.path.exists(config_file):
raise FileNotFoundError(f"配置文件不存在: {config_file}")
with open(config_file, 'r', encoding='utf-8') as f:
config_data = yaml.safe_load(f)
# 解析数据库配置
db_config = DatabaseConfig(**config_data['database'])
# 解析应用配置
app_data = config_data['app']
app_data['database'] = db_config
self._config = AppConfig(**app_data)
@property
def config(self) -> AppConfig:
"""获取配置"""
return self._config
@property
def environment(self) -> str:
"""获取当前环境"""
return self._env
@property
def database_url(self) -> str:
"""获取数据库连接URL"""
db = self._config.database
return f"mysql+pymysql://{db.username}:{db.password}@{db.host}:{db.port}/{db.database}?charset={db.charset}"
# 全局配置实例
settings = Settings()

View File

@ -0,0 +1 @@
# 控制器包

View File

@ -0,0 +1,37 @@
from fastapi import APIRouter
from app.config.settings import settings
router = APIRouter(tags=["基础接口"])
@router.get("/")
async def root():
"""根路径"""
return {
"message": "WMS FastAPI 系统",
"app_name": settings.config.name,
"environment": settings.environment,
"debug": settings.config.debug,
"version": "1.0.0"
}
@router.get("/config")
async def get_config():
"""获取配置信息"""
return {
"app": {
"name": settings.config.name,
"debug": settings.config.debug,
"host": settings.config.host,
"port": settings.config.port
},
"environment": settings.environment,
"database": {
"host": settings.config.database.host,
"port": settings.config.database.port,
"database": settings.config.database.database,
"username": settings.config.database.username,
"charset": settings.config.database.charset
}
}

View File

@ -0,0 +1,44 @@
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from app.utils.database import get_db, DatabaseUtils
from typing import Optional
router = APIRouter(prefix="/database", tags=["数据库测试"])
@router.get("/test")
async def test_database_connection():
"""测试数据库连接"""
return DatabaseUtils.test_connection()
@router.get("/tables")
async def list_all_tables():
"""获取所有表名"""
return DatabaseUtils.get_all_tables()
@router.get("/query")
async def execute_query(
sql: str = Query(..., description="要执行的SQL查询语句"),
limit: int = Query(100, description="返回记录数限制", ge=1, le=1000)
):
"""执行SQL查询"""
return DatabaseUtils.execute_query(sql, limit)
@router.get("/table/{table_name}")
async def list_table_data(
table_name: str,
limit: int = Query(100, description="返回记录数限制", ge=1, le=1000)
):
"""查看指定表的数据"""
sql = f"SELECT * FROM {table_name}"
return DatabaseUtils.execute_query(sql, limit)
@router.get("/table/{table_name}/structure")
async def get_table_structure(table_name: str):
"""获取表结构"""
sql = f"DESCRIBE {table_name}"
return DatabaseUtils.execute_query(sql, 1000)

View File

@ -0,0 +1,31 @@
from fastapi import APIRouter, Depends, Query, HTTPException
from sqlalchemy.orm import Session
from typing import List
from app.utils.database import get_db
from app.services.location_service import LocationService
from app.schemas.location import LocationQueryRequest
from app.schemas.common import BaseWmsApiResponse, WmsApiResponse
from app.models.location import TAppLocation
router = APIRouter(prefix="/wms/location", tags=["库位管理"])
def get_location_service(db: Session = Depends(get_db)) -> LocationService:
return LocationService(db)
@router.post("/genLocations", response_model=BaseWmsApiResponse)
async def gen_locations(
location_query: LocationQueryRequest,
location_service: LocationService = Depends(get_location_service)
) -> BaseWmsApiResponse:
return location_service.gen_locations(location_query)
@router.get("/getUsedLocations", response_model=WmsApiResponse)
async def get_used_locations(
equipment_id: int = Query(..., description="设备ID"),
location_type: int = Query(..., description="库位类型"),
location_service: LocationService = Depends(get_location_service)
) -> WmsApiResponse:
return location_service.get_used_locations(equipment_id, location_type)

50
app/main.py Normal file
View File

@ -0,0 +1,50 @@
from fastapi import FastAPI
from app.config.settings import settings
from app.utils.database import init_database, close_database
from app.controllers import base_controller, database_controller, location_controller
# 创建FastAPI应用
app = FastAPI(
title=settings.config.name,
description="仓库管理系统 API",
version="1.0.0",
debug=settings.config.debug
)
@app.on_event("startup")
async def startup_event():
"""应用启动事件"""
print("=" * 50)
print(f"🚀 启动 {settings.config.name}")
print(f"📦 环境: {settings.environment}")
print(f"🔧 调试模式: {settings.config.debug}")
print(f"🌐 地址: http://{settings.config.host}:{settings.config.port}")
print("=" * 50)
# 初始化数据库
init_database()
@app.on_event("shutdown")
async def shutdown_event():
"""应用关闭事件"""
print("🔄 正在关闭应用...")
close_database()
print("✅ 应用已关闭")
# 注册路由
app.include_router(base_controller.router)
app.include_router(database_controller.router)
app.include_router(location_controller.router)
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"app.main:app",
host=settings.config.host,
port=settings.config.port,
reload=settings.config.debug
)

1
app/models/__init__.py Normal file
View File

@ -0,0 +1 @@
# 数据模型包

45
app/models/location.py Normal file
View File

@ -0,0 +1,45 @@
from sqlalchemy import Column, String, Integer, Text
from app.utils.database import Base
class TAppLocation(Base):
__tablename__ = "t_app_location"
location_id = Column(String(50), primary_key=True, comment="库位ID")
location_name = Column(String(100), default="", comment="库位名称")
location_type = Column(Integer, default=1, comment="库位类型")
is_occupy = Column(Integer, default=0, comment="是否占用 0-未占用 1-已占用")
is_enable = Column(Integer, default=0, comment="是否启用")
equipment_id = Column(Integer, default=0, comment="设备ID")
aisle_num_left = Column(Integer, comment="左侧巷道号")
aisle_num_right = Column(Integer, comment="右侧巷道号")
l_row = Column(Integer, comment="")
l_col = Column(Integer, comment="")
l_layer = Column(Integer, comment="")
l_depth = Column(Integer, comment="深度")
remark = Column(Text, default="", comment="备注")
area_id = Column(Integer, default=1, comment="区域ID")
sub_area = Column(String(50), default="", comment="子区域")
def __init__(self, location_id=None, location_name="", location_type=1,
is_occupy=0, is_enable=0, equipment_id=0, aisle_num_left=None,
aisle_num_right=None, l_row=None, l_col=None, l_layer=None,
l_depth=None, remark="", area_id=1, sub_area=""):
self.location_id = location_id
self.location_name = location_name
self.location_type = location_type
self.is_occupy = is_occupy
self.is_enable = is_enable
self.equipment_id = equipment_id
self.aisle_num_left = aisle_num_left
self.aisle_num_right = aisle_num_right
self.l_row = l_row
self.l_col = l_col
self.l_layer = l_layer
self.l_depth = l_depth
self.remark = remark
self.area_id = area_id
self.sub_area = sub_area
def __repr__(self):
return f"<TAppLocation(location_id='{self.location_id}', location_name='{self.location_name}')>"

1
app/schemas/__init__.py Normal file
View File

@ -0,0 +1 @@
# Pydantic模式包

41
app/schemas/common.py Normal file
View File

@ -0,0 +1,41 @@
from pydantic import BaseModel
from typing import Any, Optional
class BaseWmsApiResponse(BaseModel):
code: int
message: str
@staticmethod
def success(message: str = "操作成功"):
return BaseWmsApiResponse(
code=200,
message=message
)
@staticmethod
def error(message: str = "操作失败", code: int = 500):
return BaseWmsApiResponse(
code=code,
message=message
)
class WmsApiResponse(BaseWmsApiResponse):
data: Optional[Any] = None
@staticmethod
def success(data: Any = None, message: str = "操作成功"):
return WmsApiResponse(
code=200,
message=message,
data=data
)
@staticmethod
def error(message: str = "操作失败", code: int = 500, data: Any = None):
return WmsApiResponse(
code=code,
message=message,
data=data
)

23
app/schemas/location.py Normal file
View File

@ -0,0 +1,23 @@
from pydantic import BaseModel, Field
from typing import Optional
class LocationQueryRequest(BaseModel):
l_row: int = Field(..., description="行数", ge=1, le=100)
l_col: int = Field(..., description="列数", ge=1, le=100)
l_layer: int = Field(..., description="层数", ge=1, le=50)
l_depth: int = Field(..., description="深度", ge=1, le=50)
sub_area: str = Field(..., description="子区域标识", min_length=1, max_length=10)
area_id: Optional[int] = Field(1, description="区域ID")
class Config:
json_schema_extra = {
"example": {
"l_row": 5,
"l_col": 8,
"l_layer": 6,
"l_depth": 2,
"sub_area": "A",
"area_id": 1
}
}

1
app/services/__init__.py Normal file
View File

@ -0,0 +1 @@
# 业务服务包

View File

@ -0,0 +1,89 @@
from sqlalchemy.orm import Session
from sqlalchemy import func
from typing import List
import logging
from app.models.location import TAppLocation
from app.schemas.location import LocationQueryRequest
from app.schemas.common import BaseWmsApiResponse, WmsApiResponse
from app.utils.string_utils import pad_left
logger = logging.getLogger(__name__)
class LocationService:
def __init__(self, db: Session):
self.db = db
def gen_locations(self, location_query: LocationQueryRequest) -> BaseWmsApiResponse:
try:
new_location_list: List[TAppLocation] = []
for row in range(1, location_query.l_row + 1):
for col in range(1, location_query.l_col + 1):
for layer in range(1, location_query.l_layer + 1):
for depth in range(1, location_query.l_depth + 1):
# 生成库位ID格式子区域 + 行(2位) + "-" + 列(2位) + "-" + 层(2位) + "-" + 深度(2位)
location_id = (
f"{location_query.sub_area}"
f"{pad_left(str(row), '0', 2)}-"
f"{pad_left(str(col), '0', 2)}-"
f"{pad_left(str(layer), '0', 2)}-"
f"{pad_left(str(depth), '0', 2)}"
)
new_location = TAppLocation(
location_id=location_id,
location_name="",
location_type=1,
is_occupy=0,
is_enable=0,
equipment_id=0,
aisle_num_left=(row + 1) // 2,
aisle_num_right=(row + 1) // 2,
l_row=row,
l_col=col,
l_layer=layer,
l_depth=depth,
remark="",
area_id=location_query.area_id if location_query.area_id is not None else 1,
sub_area=location_query.sub_area
)
new_location_list.append(new_location)
self.db.add_all(new_location_list)
self.db.commit()
logger.info(f"成功创建了{len(new_location_list)}个库位")
return BaseWmsApiResponse.success(f"成功创建了{len(new_location_list)}个库位。")
except Exception as e:
self.db.rollback()
logger.error(f"创建库位失败: {str(e)}")
return BaseWmsApiResponse.error(f"创建库位失败: {str(e)}")
def get_used_locations(self, equipment_id: int, location_type: int) -> WmsApiResponse:
try:
count = self.db.query(func.count(TAppLocation.location_id)).filter(
TAppLocation.equipment_id == equipment_id,
TAppLocation.location_type == location_type,
TAppLocation.is_occupy == 1
).scalar()
# 确保count不为None
if count is None:
count = 0
return WmsApiResponse.success(data=count, message="查询成功")
except Exception as e:
# 打印完整异常信息(包含堆栈)
logger.exception("查询已使用库位时发生异常")
# 返回错误响应 - 也使用直接实例化
return WmsApiResponse.error(
code=500,
message=f"查询失败: {type(e).__name__}",
data=None
)

1
app/utils/__init__.py Normal file
View File

@ -0,0 +1 @@
# 工具包

123
app/utils/database.py Normal file
View File

@ -0,0 +1,123 @@
from sqlalchemy import create_engine, text
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from sqlalchemy.pool import QueuePool
from app.config.settings import settings
from typing import Generator
# 数据库引擎
engine = create_engine(
settings.database_url,
poolclass=QueuePool,
pool_size=10,
max_overflow=20,
pool_pre_ping=True,
pool_recycle=3600,
echo=settings.config.debug # 在调试模式下打印SQL
)
# 会话工厂
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# 基础模型类
Base = declarative_base()
def get_db() -> Generator[Session, None, None]:
db = SessionLocal()
try:
yield db
finally:
db.close()
class DatabaseUtils:
"""数据库工具类"""
@staticmethod
def test_connection() -> dict:
"""测试数据库连接"""
try:
with engine.connect() as conn:
result = conn.execute(text("SELECT 1 as test"))
row = result.fetchone()
return {
"status": "success",
"message": "数据库连接正常",
"test_result": row[0] if row else None,
"database_url": settings.database_url.replace(settings.config.database.password, "****")
}
except Exception as e:
return {
"status": "error",
"message": f"数据库连接失败: {str(e)}"
}
@staticmethod
def get_all_tables() -> dict:
"""获取所有表名"""
try:
with engine.connect() as conn:
result = conn.execute(text("SHOW TABLES"))
tables = [row[0] for row in result.fetchall()]
return {
"status": "success",
"tables": tables,
"count": len(tables)
}
except Exception as e:
return {
"status": "error",
"message": f"获取表名失败: {str(e)}"
}
@staticmethod
def execute_query(query: str, limit: int = 100) -> dict:
"""执行查询语句"""
try:
with engine.connect() as conn:
# 添加LIMIT限制
if "LIMIT" not in query.upper():
query = f"{query} LIMIT {limit}"
result = conn.execute(text(query))
# 获取列名
columns = list(result.keys()) if result.keys() else []
# 获取数据
rows = result.fetchall()
data = [dict(zip(columns, row)) for row in rows]
return {
"status": "success",
"columns": columns,
"data": data,
"count": len(data)
}
except Exception as e:
return {
"status": "error",
"message": f"查询执行失败: {str(e)}"
}
# 初始化数据库连接
def init_database():
"""初始化数据库"""
try:
# 测试连接
test_result = DatabaseUtils.test_connection()
if test_result["status"] == "success":
print(f"✓ 数据库连接成功 - 环境: {settings.environment}")
print(f"✓ 数据库: {settings.config.database.host}:{settings.config.database.port}/{settings.config.database.database}")
else:
print(f"✗ 数据库连接失败: {test_result['message']}")
except Exception as e:
print(f"✗ 数据库初始化错误: {str(e)}")
def close_database():
"""关闭数据库连接"""
engine.dispose()
print("数据库连接已关闭")

14
app/utils/string_utils.py Normal file
View File

@ -0,0 +1,14 @@
def pad_left(text: str, pad_char: str = "0", length: int = 2) -> str:
return text.rjust(length, pad_char)
def pad_right(text: str, pad_char: str = " ", length: int = 10) -> str:
return text.ljust(length, pad_char)
def is_empty(text: str) -> bool:
return text is None or text.strip() == ""
def is_not_empty(text: str) -> bool:
return not is_empty(text)

12
config/base.yaml Normal file
View File

@ -0,0 +1,12 @@
app:
name: wms_main
max_file_size: 104857600 # 100MB
max_request_size: 1048576000 # 1000MB
server:
context_path: "/"
database:
driver: mysql+pymysql
port: 3306
params: characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&rewriteBatchedStatements=true

13
config/development.yaml Normal file
View File

@ -0,0 +1,13 @@
app:
name: "wms_main"
debug: true
host: "localhost"
port: 12315
database:
host: "localhost"
port: 3306
username: "root"
password: "root"
database: "wms_ntyc"
charset: "utf8mb4"

13
config/production.yaml Normal file
View File

@ -0,0 +1,13 @@
app:
name: "wms_main"
debug: false
host: "localhost"
port: 12315
database:
host: "10.18.58.21"
port: 3306
username: "user"
password: "user"
database: "wms_yachi_nantong"
charset: "utf8mb4"

8
requirements.txt Normal file
View File

@ -0,0 +1,8 @@
fastapi
uvicorn
pydantic
pyyaml
python-dotenv
sqlalchemy
pymysql
python-multipart

59
run.py Normal file
View File

@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""
WMS FastAPI 应用启动脚本
支持通过环境变量切换开发/生产环境
"""
import os
import sys
import uvicorn
from pathlib import Path
# 添加项目根目录到Python路径
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
def main():
"""主函数"""
# 获取环境变量,默认为开发环境
env = os.getenv("ENVIRONMENT", "development")
print(f"🔧 当前环境: {env}")
print(f"📁 项目根目录: {project_root}")
# 设置环境变量
os.environ["ENVIRONMENT"] = env
try:
from app.config.settings import settings
print(f"🚀 启动FastAPI应用...")
print(f"📦 应用名称: {settings.config.name}")
print(f"🌐 访问地址: http://{settings.config.host}:{settings.config.port}")
print(f"📚 API文档: http://{settings.config.host}:{settings.config.port}/docs")
# 根据环境设置不同的启动参数
if env == "development":
uvicorn.run(
"app.main:app",
host=settings.config.host,
port=settings.config.port,
reload=True,
log_level="debug"
)
else:
uvicorn.run(
"app.main:app",
host=settings.config.host,
port=settings.config.port,
reload=False,
workers=4
)
except Exception as e:
print(f"❌ 启动失败: {e}")
sys.exit(1)
if __name__ == "__main__":
main()

11
test_main.http Normal file
View File

@ -0,0 +1,11 @@
# Test your FastAPI endpoints
GET http://127.0.0.1:8000/
Accept: application/json
###
GET http://127.0.0.1:8000/hello/User
Accept: application/json
###

128
test_setup.py Normal file
View File

@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""
快速测试脚本验证项目配置是否正确
"""
import os
import sys
from pathlib import Path
# 添加项目根目录到Python路径
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
def test_config():
"""测试配置加载"""
print("🔧 测试配置加载...")
# 测试开发环境配置
os.environ["ENVIRONMENT"] = "development"
try:
from app.config.settings import settings
print(f"✅ 开发环境配置加载成功")
print(f" 应用名称: {settings.config.name}")
print(
f" 数据库: {settings.config.database.host}:{settings.config.database.port}/{settings.config.database.database}")
print(f" 调试模式: {settings.config.debug}")
except Exception as e:
print(f"❌ 开发环境配置加载失败: {e}")
return False
return True
def test_database():
"""测试数据库连接"""
print("\n🔍 测试数据库连接...")
try:
from app.utils.database import DatabaseUtils
result = DatabaseUtils.test_connection()
if result["status"] == "success":
print(f"✅ 数据库连接成功")
print(f" 测试结果: {result['test_result']}")
# 测试获取表名
tables_result = DatabaseUtils.get_all_tables()
if tables_result["status"] == "success":
print(f"✅ 获取表名成功,共 {tables_result['count']} 张表")
if tables_result["tables"]:
print(f" 前5张表: {tables_result['tables'][:5]}")
else:
print(f"⚠️ 获取表名失败: {tables_result['message']}")
else:
print(f"❌ 数据库连接失败: {result['message']}")
return False
except Exception as e:
print(f"❌ 数据库测试失败: {e}")
return False
return True
def test_app_start():
"""测试应用是否可以正常启动"""
print("\n🚀 测试应用启动...")
try:
from app.main import app
print(f"✅ FastAPI应用创建成功")
print(f" 应用标题: {app.title}")
print(f" 路由数量: {len(app.routes)}")
# 检查路由
routes = [route.path for route in app.routes if hasattr(route, 'path')]
print(f" 主要路由: {routes}")
except Exception as e:
print(f"❌ 应用启动测试失败: {e}")
return False
return True
def main():
"""主测试函数"""
print("=" * 50)
print("🧪 WMS FastAPI 项目配置测试")
print("=" * 50)
tests = [
("配置加载", test_config),
("数据库连接", test_database),
("应用启动", test_app_start)
]
passed = 0
failed = 0
for test_name, test_func in tests:
try:
if test_func():
passed += 1
else:
failed += 1
except Exception as e:
print(f"{test_name}测试异常: {e}")
failed += 1
print("\n" + "=" * 50)
print(f"📊 测试结果: 通过 {passed}, 失败 {failed}")
if failed == 0:
print("🎉 所有测试通过!项目配置正确,可以启动应用。")
print("\n💡 启动命令:")
print(" python run.py")
print("\n📚 访问文档:")
print(" http://localhost:12315/docs")
else:
print("⚠️ 部分测试失败,请检查配置。")
print("=" * 50)
if __name__ == "__main__":
main()