12 KiB
12 KiB
ExamService
题库小程序服务端 - FastAPI实现
项目结构
ExamService/
├── alembic/ # 数据库迁移相关
├── mooc/ # 主应用目录
│ ├── api/ # API路由
│ │ └── v1/ # API v1版本
│ ├── core/ # 核心配置
│ ├── crud/ # 数据库操作
│ ├── db/ # 数据库
│ ├── models/ # 数据库模型
│ ├── schemas/ # Pydantic模型
│ └── utils/ # 工具函数
├── tests/ # 测试目录
└── [配置文件]
安装
1. 安装 Miniconda
在 Ubuntu 上安装 Miniconda,执行以下步骤:
# 下载 Miniconda 安装脚本
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
# 给安装脚本添加执行权限
chmod +x Miniconda3-latest-Linux-x86_64.sh
# 运行安装脚本
./Miniconda3-latest-Linux-x86_64.sh
# 按照提示完成安装,选择安装目录并接受许可协议
# 激活 Miniconda
source ~/.bashrc
# 创建名为 'mooc' 的虚拟环境,Python 版本大于等于 3.13
conda create --name mooc python>=3.13
# 激活 'mooc' 环境
conda activate mooc
# 安装依赖
pip install -r requirements.txt
安装mysql开发库
# 更新包列表
apt-get update
# 安装 MySQL 开发包和其他必要的包
apt-get install -y python3-dev default-libmysqlclient-dev build-essential pkg-config
pip install mysqlclient pymysql cryptography
运行
uvicorn main:app --reload
初始化数据库
alembic init alembic
alembic revision --autogenerate -m "Initial migration"
alembic upgrade head
数据库管理
添加新的数据库表
- 创建模型文件:
将同类型的表模型放在同一个文件中。文件命名规则:去除表名的 ims_
前缀,例如:
ims_account_wechats
、ims_account_wxapp
等表 →account.py
ims_uni_account
、ims_uni_settings
等表 →uni_account.py
# filepath: mooc/models/account.py
from sqlalchemy import Column, Integer, String, SmallInteger
from mooc.db.database import Base
class AccountWechats(Base):
"""微信公众号账号表"""
__tablename__ = "ims_account_wechats"
acid = Column(Integer, primary_key=True)
uniacid = Column(Integer, nullable=False)
# ... 其他字段
- 在
mooc/models/__init__.py
中注册表:
# 添加到 expected_tables 集合中
expected_tables = {
# ... 现有表 ...
'ims_account_wechats', # 新添加的表
}
- 导出模型类:
# 在 mooc/models/__init__.py 中添加导出
from mooc.models.account import AccountWechats
- 创建 Schema:
# filepath: mooc/schemas/account.py
from pydantic import BaseModel
class AccountWechatsBase(BaseModel):
uniacid: int
# ... 其他字段
class AccountWechatsCreate(AccountWechatsBase):
pass
class AccountWechatsUpdate(BaseModel):
# 可选字段用于更新
uniacid: Optional[int] = None
class AccountWechatsRead(AccountWechatsBase):
acid: int
class Config:
from_attributes = True
- 创建 CRUD 操作:
# filepath: mooc/crud/crud_account.py
from mooc.crud.crud_base import CRUDBase
class CRUDAccountWechats(CRUDBase[AccountWechats, AccountWechatsCreate, AccountWechatsUpdate]):
def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[AccountWechats]:
return self.get_by_field(db, "uniacid", uniacid)
# 创建实例
account_wechats = CRUDAccountWechats(AccountWechats)
- 添加 API 路由:
# filepath: mooc/api/v1/endpoints/account.py
@account_router.post("/wechats", response_model=AccountWechatsRead)
def create_wechat_account(
*,
db: Session = Depends(deps.get_db),
account_in: AccountWechatsCreate,
):
"""创建微信公众号账号"""
return account_wechats.create(db=db, obj_in=account_in)
数据库表自动创建
应用启动时会自动执行以下操作:
- 导入并验证所有模型定义
- 检查数据库中缺失的表
- 自动创建缺失的表
你可以通过查看应用日志了解表创建的详细情况:
python main.py
注意事项
- 模型类名采用帕斯卡命名法(PascalCase),去除
ims_
前缀 - 表名(
__tablename__
)必须与数据库中的实际表名完全一致 - 字段类型要与数据库定义严格匹配
- 确保主键和索引的正确定义
- 添加适当的文档字符串说明表的用途
调试帮助
如果遇到问题,可以:
- 检查日志输出了解具体错误
- 使用
get_all_table_names()
查看已注册的表 - 验证模型定义是否完整
- 确认数据库连接配置是否正确
CRUDBase 使用指南
简介
CRUDBase是一个通用的CRUD基类,提供了基础的增删改查操作。通过继承这个基类,可以快速实现特定模型的CRUD操作。
使用方法
- 创建CRUD类:
from mooc.crud.base import CRUDBase
from mooc.models.your_model import YourModel
from mooc.schemas.your_schema import YourCreateSchema, YourUpdateSchema
class CRUDYourModel(CRUDBase[YourModel, YourCreateSchema, YourUpdateSchema]):
pass # 继承基础功能即可,如需自定义方法可以在这里添加
- 实例化CRUD对象:
crud_your_model = CRUDYourModel(YourModel)
基础操作示例
# 创建记录
new_item = crud_your_model.create(db, obj_in=item_create_schema)
# 获取单条记录
item = crud_your_model.get(db, id=123)
# 获取多条记录
items = crud_your_model.get_multi(db, skip=0, limit=100)
# 更新记录
updated_item = crud_your_model.update(db, db_obj=existing_item, obj_in=item_update_schema)
# 删除记录
crud_your_model.remove(db, id=123)
自定义方法示例
class CRUDYourModel(CRUDBase[YourModel, YourCreateSchema, YourUpdateSchema]):
def get_by_custom_field(self, db: Session, field_value: str) -> Optional[YourModel]:
return db.query(self.model).filter(self.model.custom_field == field_value).first()
注意事项
-
CRUDBase需要三个类型参数:
- ModelType: SQLAlchemy模型类
- CreateSchemaType: Pydantic创建模型
- UpdateSchemaType: Pydantic更新模型
-
所有方法都需要传入数据库会话(db: Session)参数
-
更新操作支持两种方式:
- 字典形式:
update(db, db_obj=item, obj_in={"field": "new_value"})
- Pydantic模型:
update(db, db_obj=item, obj_in=ItemUpdate(field="new_value"))
- 字典形式:
API文档
启动服务后访问: http://localhost:2333/docs
使用 AI 生成模型代码
当你有一个新的数据库表需要添加时,可以使用以下方式让 AI 帮助生成代码:
- 准备 SQL 建表语句,例如:
CREATE TABLE `ims_uni_settings` (
`uniacid` int(10) UNSIGNED NOT NULL,
`passport` varchar(200) NOT NULL,
`oauth` varchar(100) NOT NULL,
-- ... 其他字段
PRIMARY KEY (`uniacid`)
) ENGINE = MyISAM CHARACTER SET = utf8 COLLATE = utf8_general_ci;
- 使用以下提示词模板:
请帮我为这个表生成 FastAPI + SQLAlchemy 的实现,包括:
1. SQLAlchemy 模型(models/xxx.py)
2. Pydantic Schema(schemas/xxx.py)
3. CRUD 操作(crud/crud_xxx.py)
4. API 路由(api/v1/endpoints/xxx.py)
要求:
- 遵循项目的命名规范(去除ims_前缀,使用帕斯卡命名法)
- 确保字段类型映射正确
- 包含必要的文档注释
- 实现基本的 CRUD 操作和常用查询方法
[SQL建表语句]
- 对生成的代码进行以下检查:
- 表名是否与数据库一致
- 字段类型是否正确映射
- 主键和索引是否正确定义
- 是否符合项目的命名规范
- CRUD 方法是否满足业务需求
- 常用的 SQL 类型映射参考:
# MySQL 到 SQLAlchemy 的类型映射
int/bigint -> Integer
varchar/char -> String(length)
text -> Text
datetime -> DateTime
timestamp -> TIMESTAMP
tinyint -> SmallInteger
decimal -> DECIMAL(precision, scale)
- 示例提示词:
我需要为以下数据表生成 FastAPI 实现:
- 表名:ims_uni_settings
- 主键:uniacid
- 特殊需求:
* 需要通过 uniacid 查询的方法
* 需要处理 Text 类型的 payment 字段
* 所有字符串字段都是必填的
[SQL语句]
...
请生成完整的代码实现。
- 代码生成后的集成步骤:
- 将模型类添加到对应的 models 文件
- 在 models/init.py 中注册表名
- 在 api.py 中注册新的路由
- 检查并测试生成的 API 端点
这种方式可以:
- 快速生成符合项目规范的代码
- 确保类型映射的准确性
- 保持代码结构的一致性
- 减少手动编码的错误
PHP 到 FastAPI 迁移指南
全局变量对应关系
$_W
(WeEngine全局配置):
$_W = array(
'config' => array(), // 系统配置信息
'timestamp' => time(), // 当前时间戳
'charset' => 'utf8', // 字符集
'clientip' => '', // 客户端IP
'uniacid' => 0, // 当前统一公众号ID
'acid' => 0, // 当前账号ID
'uid' => 0, // 当前用户ID
'isajax' => false, // 是否AJAX请求
'ispost' => false, // 是否POST请求
'siteroot' => '', // 站点根目录URL
'siteurl' => '', // 当前URL
'attachurl' => '', // 附件URL
'setting' => array(), // 站点设置
'module' => array() // 当前模块信息
);
$_GPC
(全局请求参数):
$_GPC = array_merge(
$_GET, // GET参数
$_POST, // POST参数
$_COOKIE // COOKIE数据
);
迁移方法步骤
以迁移 dopageLogin
为例:
- 在
wxapp.py
中添加路由:
@wxapp_router.post("/index")
async def handle_wxapp_request(
request: Request,
i: str, # uniacid
do: Optional[str] = None,
db: Session = Depends(get_db)
):
# 处理不同的 do 参数
if do == "Login":
return await handle_login(request, i, db)
- 创建请求数据模型:
class LoginRequest(BaseModel):
code: str
encryptedData: Optional[str] = None
iv: Optional[str] = None
- 创建处理函数:
async def handle_login(request: Request, uniacid: str, db: Session):
# 获取所有参数
params = await get_all_params(request)
# 验证必要参数
if not params.get("code"):
return {"code": 1, "msg": "code值获取失败"}
# 获取小程序配置
account = await get_account_info(db, uniacid)
# 业务逻辑处理
try:
result = await process_login(
db,
code=params["code"],
appid=account.key,
secret=account.secret,
encrypted_data=params.get("encryptedData"),
iv=params.get("iv")
)
return {"code": 0, "msg": "登录成功", "data": result}
except Exception as e:
return {"code": 1, "msg": str(e)}
- 实现数据库操作:
# 在 crud/crud_goouc_fullexam_user.py 中
class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
async def get_by_openid(self, db: Session, *, openid: str) -> Optional[User]:
return db.query(self.model).filter(
self.model.openid == openid,
self.model.istatus == 1
).first()
- 实现业务逻辑:
# 在 services/user.py 中
async def process_login(
db: Session,
code: str,
appid: str,
secret: str,
encrypted_data: Optional[str] = None,
iv: Optional[str] = None
) -> Dict:
# 1. 调用微信API获取session_key和openid
# 2. 解密用户信息
# 3. 查找或创建用户
# 4. 更新登录时间
# 5. 返回结果
注意事项
- 参数获取:
- PHP:
$_GPC["param"]
- FastAPI:
params = await get_all_params(request)
- 数据库操作:
- PHP:
pdo_fetch("SELECT * FROM " . tablename($this->t_user))
- FastAPI:
db.query(User).filter(User.id == uid).first()
- 返回格式:
- PHP:
$this->result(0, "success", $data)
- FastAPI:
{"code": 0, "msg": "success", "data": data}
- 错误处理:
try:
# 业务逻辑
return {"code": 0, "msg": "success", "data": result}
except Exception as e:
return {"code": 1, "msg": str(e)}
- 表名处理:
- PHP:
tablename($this->t_user)
- FastAPI: 在模型定义中指定
__tablename__ = "ims_goouc_fullexam_user"