Program/README.md

493 lines
12 KiB
Markdown
Raw Normal View History

2024-12-31 22:27:04 +08:00
# ExamService
题库小程序服务端 - FastAPI实现
## 项目结构
```
ExamService/
├── alembic/ # 数据库迁移相关
├── mooc/ # 主应用目录
│ ├── api/ # API路由
│ │ └── v1/ # API v1版本
│ ├── core/ # 核心配置
│ ├── crud/ # 数据库操作
│ ├── db/ # 数据库
│ ├── models/ # 数据库模型
│ ├── schemas/ # Pydantic模型
│ └── utils/ # 工具函数
├── tests/ # 测试目录
└── [配置文件]
```
## 安装
### 1. 安装 Miniconda
在 Ubuntu 上安装 Miniconda执行以下步骤
```bash
# 下载 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开发库
```bash
# 更新包列表
apt-get update
# 安装 MySQL 开发包和其他必要的包
apt-get install -y python3-dev default-libmysqlclient-dev build-essential pkg-config
pip install mysqlclient pymysql cryptography
```
## 运行
```bash
uvicorn main:app --reload
```
## 初始化数据库
```bash
alembic init alembic
alembic revision --autogenerate -m "Initial migration"
alembic upgrade head
```
2025-01-04 01:13:47 +08:00
## 数据库管理
2024-12-31 22:27:04 +08:00
2025-01-04 01:13:47 +08:00
### 添加新的数据库表
2024-12-31 22:27:04 +08:00
2025-01-04 01:13:47 +08:00
1. 创建模型文件:
2024-12-31 22:27:04 +08:00
2025-01-04 01:13:47 +08:00
将同类型的表模型放在同一个文件中。文件命名规则:去除表名的 `ims_` 前缀,例如:
- `ims_account_wechats`、`ims_account_wxapp` 等表 → `account.py`
- `ims_uni_account`、`ims_uni_settings` 等表 → `uni_account.py`
2024-12-31 22:27:04 +08:00
2025-01-04 01:13:47 +08:00
```python
# filepath: mooc/models/account.py
2024-12-31 22:27:04 +08:00
from sqlalchemy import Column, Integer, String, SmallInteger
from mooc.db.database import Base
2025-01-04 01:13:47 +08:00
2024-12-31 22:27:04 +08:00
class AccountWechats(Base):
2025-01-04 01:13:47 +08:00
"""微信公众号账号表"""
2024-12-31 22:27:04 +08:00
__tablename__ = "ims_account_wechats"
2025-01-04 01:13:47 +08:00
2024-12-31 22:27:04 +08:00
acid = Column(Integer, primary_key=True)
uniacid = Column(Integer, nullable=False)
2025-01-04 01:13:47 +08:00
# ... 其他字段
```
2024-12-31 22:27:04 +08:00
2025-01-04 01:13:47 +08:00
2.`mooc/models/__init__.py` 中注册表:
```python
# 添加到 expected_tables 集合中
expected_tables = {
# ... 现有表 ...
'ims_account_wechats', # 新添加的表
}
2024-12-31 22:27:04 +08:00
```
2025-01-04 01:13:47 +08:00
3. 导出模型类:
2024-12-31 22:27:04 +08:00
```python
2025-01-04 01:13:47 +08:00
# 在 mooc/models/__init__.py 中添加导出
from mooc.models.account import AccountWechats
```
4. 创建 Schema:
2024-12-31 22:27:04 +08:00
2025-01-04 01:13:47 +08:00
```python
# filepath: mooc/schemas/account.py
2024-12-31 22:27:04 +08:00
from pydantic import BaseModel
class AccountWechatsBase(BaseModel):
2025-01-04 01:13:47 +08:00
uniacid: int
# ... 其他字段
2024-12-31 22:27:04 +08:00
class AccountWechatsCreate(AccountWechatsBase):
pass
class AccountWechatsUpdate(BaseModel):
2025-01-04 01:13:47 +08:00
# 可选字段用于更新
uniacid: Optional[int] = None
2024-12-31 22:27:04 +08:00
2025-01-04 01:13:47 +08:00
class AccountWechatsRead(AccountWechatsBase):
acid: int
2024-12-31 22:27:04 +08:00
class Config:
2025-01-04 01:13:47 +08:00
from_attributes = True
2024-12-31 22:27:04 +08:00
```
2025-01-04 01:13:47 +08:00
5. 创建 CRUD 操作:
```python
# filepath: mooc/crud/crud_account.py
2025-01-03 18:10:40 +08:00
from mooc.crud.crud_base import CRUDBase
2024-12-31 22:27:04 +08:00
2025-01-03 18:10:40 +08:00
class CRUDAccountWechats(CRUDBase[AccountWechats, AccountWechatsCreate, AccountWechatsUpdate]):
def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[AccountWechats]:
2025-01-04 01:13:47 +08:00
return self.get_by_field(db, "uniacid", uniacid)
2024-12-31 22:27:04 +08:00
2025-01-04 01:13:47 +08:00
# 创建实例
account_wechats = CRUDAccountWechats(AccountWechats)
2024-12-31 22:27:04 +08:00
```
2025-01-03 18:10:40 +08:00
2025-01-04 01:13:47 +08:00
6. 添加 API 路由:
2024-12-31 22:27:04 +08:00
```python
2025-01-04 01:13:47 +08:00
# 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)
2024-12-31 22:27:04 +08:00
```
2025-01-04 01:13:47 +08:00
### 数据库表自动创建
应用启动时会自动执行以下操作:
1. 导入并验证所有模型定义
2. 检查数据库中缺失的表
3. 自动创建缺失的表
你可以通过查看应用日志了解表创建的详细情况:
2024-12-31 22:27:04 +08:00
```bash
python main.py
```
2025-01-04 01:13:47 +08:00
### 注意事项
1. 模型类名采用帕斯卡命名法PascalCase去除 `ims_` 前缀
2. 表名(`__tablename__`)必须与数据库中的实际表名完全一致
3. 字段类型要与数据库定义严格匹配
4. 确保主键和索引的正确定义
5. 添加适当的文档字符串说明表的用途
### 调试帮助
如果遇到问题,可以:
1. 检查日志输出了解具体错误
2. 使用 `get_all_table_names()` 查看已注册的表
3. 验证模型定义是否完整
4. 确认数据库连接配置是否正确
2025-01-03 18:03:30 +08:00
## CRUDBase 使用指南
### 简介
CRUDBase是一个通用的CRUD基类提供了基础的增删改查操作。通过继承这个基类可以快速实现特定模型的CRUD操作。
### 使用方法
1. 创建CRUD类:
```python
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 # 继承基础功能即可,如需自定义方法可以在这里添加
```
2. 实例化CRUD对象:
```python
crud_your_model = CRUDYourModel(YourModel)
```
### 基础操作示例
```python
# 创建记录
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)
```
### 自定义方法示例
```python
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()
```
### 注意事项
1. CRUDBase需要三个类型参数
- ModelType: SQLAlchemy模型类
- CreateSchemaType: Pydantic创建模型
- UpdateSchemaType: Pydantic更新模型
2. 所有方法都需要传入数据库会话(db: Session)参数
3. 更新操作支持两种方式:
- 字典形式:`update(db, db_obj=item, obj_in={"field": "new_value"})`
- Pydantic模型`update(db, db_obj=item, obj_in=ItemUpdate(field="new_value"))`
2024-12-31 22:27:04 +08:00
## API文档
启动服务后访问: http://localhost:2333/docs
2025-01-04 01:13:47 +08:00
### 使用 AI 生成模型代码
当你有一个新的数据库表需要添加时,可以使用以下方式让 AI 帮助生成代码:
1. 准备 SQL 建表语句,例如:
```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;
```
2. 使用以下提示词模板:
```
请帮我为这个表生成 FastAPI + SQLAlchemy 的实现,包括:
1. SQLAlchemy 模型models/xxx.py
2. Pydantic Schemaschemas/xxx.py
3. CRUD 操作crud/crud_xxx.py
4. API 路由api/v1/endpoints/xxx.py
要求:
- 遵循项目的命名规范去除ims_前缀使用帕斯卡命名法
- 确保字段类型映射正确
- 包含必要的文档注释
- 实现基本的 CRUD 操作和常用查询方法
[SQL建表语句]
```
3. 对生成的代码进行以下检查:
- 表名是否与数据库一致
- 字段类型是否正确映射
- 主键和索引是否正确定义
- 是否符合项目的命名规范
- CRUD 方法是否满足业务需求
4. 常用的 SQL 类型映射参考:
```python
# MySQL 到 SQLAlchemy 的类型映射
int/bigint -> Integer
varchar/char -> String(length)
text -> Text
datetime -> DateTime
timestamp -> TIMESTAMP
tinyint -> SmallInteger
decimal -> DECIMAL(precision, scale)
```
5. 示例提示词:
```
我需要为以下数据表生成 FastAPI 实现:
- 表名ims_uni_settings
- 主键uniacid
- 特殊需求:
* 需要通过 uniacid 查询的方法
* 需要处理 Text 类型的 payment 字段
* 所有字符串字段都是必填的
[SQL语句]
...
请生成完整的代码实现。
```
6. 代码生成后的集成步骤:
- 将模型类添加到对应的 models 文件
- 在 models/__init__.py 中注册表名
- 在 api.py 中注册新的路由
- 检查并测试生成的 API 端点
这种方式可以:
- 快速生成符合项目规范的代码
- 确保类型映射的准确性
- 保持代码结构的一致性
- 减少手动编码的错误
2025-01-15 21:03:36 +08:00
## PHP 到 FastAPI 迁移指南
### 全局变量对应关系
1. `$_W` (WeEngine全局配置):
```php
$_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() // 当前模块信息
);
2025-01-15 21:03:36 +08:00
```
2. `$_GPC` (全局请求参数):
```php
$_GPC = array_merge(
$_GET, // GET参数
$_POST, // POST参数
$_COOKIE // COOKIE数据
);
2025-01-15 21:03:36 +08:00
```
### 迁移方法步骤
以迁移 `dopageLogin` 为例:
1.`wxapp.py` 中添加路由:
```python
@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)
```
2. 创建请求数据模型:
```python
class LoginRequest(BaseModel):
code: str
encryptedData: Optional[str] = None
iv: Optional[str] = None
```
3. 创建处理函数:
```python
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)}
```
4. 实现数据库操作:
```python
# 在 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()
```
5. 实现业务逻辑:
```python
# 在 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. 返回结果
```
### 注意事项
1. 参数获取:
- PHP: `$_GPC["param"]`
- FastAPI: `params = await get_all_params(request)`
2. 数据库操作:
- PHP: `pdo_fetch("SELECT * FROM " . tablename($this->t_user))`
- FastAPI: `db.query(User).filter(User.id == uid).first()`
3. 返回格式:
- PHP: `$this->result(0, "success", $data)`
- FastAPI: `{"code": 0, "msg": "success", "data": data}`
4. 错误处理:
```python
try:
# 业务逻辑
return {"code": 0, "msg": "success", "data": result}
except Exception as e:
return {"code": 1, "msg": str(e)}
```
5. 表名处理:
- PHP: `tablename($this->t_user)`
- FastAPI: 在模型定义中指定 `__tablename__ = "ims_goouc_fullexam_user"`