前后端分离项目实现的一个后端管理框架
- 前端:vue3 + element plus
- 后端:fastapi + sqlmodel
- 任务管理: rpyc+apscheduler
个人学习项目,只会对明显的 bug 进行修复,不会进行过多新功能的更新
常用分支说明:
- main:主分支,所有功能都会往这里合并
- test: 测试分支
项目根目录提供了 .env.example 文件作为环境变量的模板。
-
开发环境: 复制
.env.example为.env.dev,根据需要修改变量(通常默认值即可用于本地开发)。cp .env.example .env.dev
-
生产环境: 复制
.env.example为.env.prod,并修改相关配置(如NODE_ENV,SECRET_KEY, 数据库密码等)。cp .env.example .env.prod
注意:.env 和 .env.* (除了 .env.example) 已被加入 .gitignore,请勿提交包含敏感信息的配置文件。
开发环境使用 docker compose --env-file .env.dev up -d
构建生产镜像
-
修改.env.prod 中打包镜像版本信息
BACKEND_IMAGE=fastapi-admin/backend-prod:2.1.0 FRONTEND_IMAGE=fastapi-admin/frontend-prod:2.1.0 -
打包生成生产镜像
docker compose -f docker-compose-build.yml --env-file .env.prod build --no-cache -
手动导出与导入镜像
#导出镜像 docker save -o <输出文件名.tar> <镜像名:标签> #导入镜像 docker load -i <镜像文件.tar>
- nginx 添加配置:
参考项目目录下的 `nginx/dev.conf` 文件。
- 前端执行打包命令
npm run build
- 添加 nginx 配置
参考项目目录下的 `nginx/prod.conf` 文件。
- dev:开发环境,使用 uvicorn 启动
- pro:生产环境,使用 gunicorn 启动
./service.sh {dev|pro} {start|stop|restart}
后端需要修改对应配置文件信息
- dev: config/development.yaml
- pro: config/production.yaml
开发模式下,uvicorn 服务能自动刷新,rpyc 服务无法自动刷新,需要手动重启
项目使用 Alembic 进行数据库版本管理和迁移,支持容器化环境。
当 Docker Compose 服务已启动时,使用 exec 命令:
# 查看当前迁移状态
docker-compose --env-file .env.dev exec backend_api alembic current
# 查看迁移历史
docker-compose --env-file .env.dev exec backend_api alembic history
# 生成新的迁移文件(自动检测模型变更)
docker-compose --env-file .env.dev exec backend_api alembic revision --autogenerate -m "迁移描述信息"
# 执行迁移到最新版本
docker-compose --env-file .env.dev exec backend_api alembic upgrade head
# 回滚到指定版本
docker-compose --env-file .env.dev exec backend_api alembic downgrade <revision_id>
# 回滚一个版本
docker-compose --env-file .env.dev exec backend_api alembic downgrade -1当 Docker Compose 服务未启动时,使用 run 命令:
# 生成新的迁移文件(自动检测模型变更)
docker compose -f .\docker-compose.yml --env-file .env.dev run backend_api alembic revision --autogenerate -m "迁移描述信息"
# 执行迁移到最新版本
docker compose -f .\docker-compose.yml --env-file .env.dev run backend_api alembic upgrade head
# 查看当前迁移状态
docker compose -f .\docker-compose.yml --env-file .env.dev run backend_api alembic current
# 查看迁移历史
docker compose -f .\docker-compose.yml --env-file .env.dev run backend_api alembic history注意: 使用 run 命令时必须指定 --env-file .env.dev 参数,否则环境变量无法正确加载。
# 进入后端目录
cd backend/app
# 查看当前迁移状态
alembic current
# 生成新的迁移文件
alembic revision --autogenerate -m "迁移描述信息"
# 执行迁移
alembic upgrade head- 迁移文件位置:
backend/app/alembic/versions/ - 配置文件:
backend/app/alembic.ini(已配置支持容器化环境变量) - 环境配置:
backend/app/alembic/env.py
- 生产环境迁移前务必备份数据库
- 迁移文件生成后,请检查并确认变更内容
- 团队开发时,及时同步迁移文件
- 避免手动修改已提交的迁移文件
- 容器化环境会自动使用环境变量中的数据库配置
- 后端数据库对于布尔值的传递统一数据库设置为 tinyint,0 为假,1 为真
- 前端所有 bool 都 0 为假,1 为真
<!-- 导入分页相关方法 -->
import usePagination from '@/composables/usePagination'
<!--定义一个搜索字段-->
const searchForm = {
name: null,
email: null,
enable: null
}
<!-- 传入查询相关字段信息 -->
const {
search,
tableData,
currentPage,
pageSize,
orderModel,
total,
freshCurrentPage,
handleSearch
} = usePagination('/api/users/search', searchForm)定义了一个分页模型
from typing import Optional, Generic, TypeVar
from pydantic import BaseModel
T = TypeVar('T')
class Pagination(BaseModel, Generic[T]):
search: T
page: Optional[int] = 1
page_size: Optional[int] = 10
model: Optional[str] = 'asc'使用
@router.post('/dict/item/search', summary="字典列表查询", response_model=ApiResponse[SearchResponse[DictRead]])
async def search_items(search: Pagination[DictItemSearch], session: Session = Depends(get_session)):
# 需要定义一个filter_type,用于区分各个字段的匹配形式,可用为:l_like、r_like、like、eq、ne、lt、le、gt、ge
filter_type = DictItemSearchFilter(dict_id='eq', label='like', enable='eq', value='like')
total = crud.internal.dict_item.search_total(session, search.search, filter_type.model_dump())
items: List[DictRead] = crud.internal.dict_item.search(session, search, filter_type.model_dump())
# 转义下数据类型,不然在执行return的时候,会去获取外键、关联字段相关的内容,容易造成数据量过多等问题
item_list = [DictRead.from_orm(item) for item in items]
return ApiResponse(
data={
'total': total,
'data': item_list
}
)通过 casbin 实现简化的权限管控功能,思路如下:
- 对于不需要 token 验证的,写入 settings 的 APISettings.NO_VERIFY_URL 中
- 对于需要权限管控的接口,写入 casbin 中,并且对需要权限验证的接口使用 casbin 验证
- 前端通过权限字段,进行显示
- 只能对按钮级别的功能实现权限管控
- 页面管控,只是后端返回菜单列表,前端根据菜单列表进行显示,后端没有对页面进行权限管控
v-permission 定义了权限标识,当拥有权限时,可以页面上能显示按钮,同时,后端也会进行权限的判断。
方式一:使用指令 (推荐简单按钮使用)
<el-button
v-permission="'role:update'"
type="primary"
size="small"
@click="handleEdit(scope.row)"
>
编辑
</el-button>方式二:使用全局函数 (推荐复杂逻辑或组件使用)
全局挂载了 $hasPermi (或 $hasPermission) 方法,可在 v-if 或 JS 中使用。
<el-button
v-if="$hasPermi('role:update')"
type="primary"
size="small"
@click="handleEdit(scope.row)"
>
编辑
</el-button>
<!-- 支持多个权限满足其一 -->
<el-button v-if="$hasPermi(['role:update', 'role:admin'])" ...>
管理
</el-button>@router.put('/roles', summary="更新角色", response_model=ApiResponse[Role],
dependencies=[Depends(Authority('role:update'))])
async def update_roles(role_info: RoleUpdate, session: Session = Depends(get_session)):
print(role_info)
if role_info.name == 'admin':
ApiResponse(code=status.HTTP_400_BAD_REQUEST, message='admin权限组无法更新信息')
db_obj = crud.internal.role.get(session, role_info.id)
enable_menus = role_info.menus
delattr(role_info, 'menus')
db_obj = crud.internal.role.update(session, db_obj, role_info)
crud.internal.role.update_menus(session, db_obj, enable_menus)
return ApiResponse(
data=db_obj
)









