常见问题
按场景分类。前端运行 / 后端运行 / 模块开发 / 权限 / 部署。
启动 & 安装
python run.py 报 "no such table"
启动时不会自动建表 / 迁移。首次必须:
make initdb # 等同 tortoise init-db + 首次种子数据
# 或
make mm # makemigrations + migrate后续模型变更也走 make mm。详见 命令参考。
Redis 连接失败
redis-cli ping # 应返回 PONG或临时用本地 Redis:
REDIS_URL=redis://localhost:6379/0端口被占用
后端 9999 / 前端 9527 / Nginx 1880。改 run.py 或 web/vite.config.ts 即可。
模块开发
重启后我手动建的菜单 / 按钮被清掉了
init_data.py 调用了 reconcile_menu_subtree(...) — 该子树进入 IaC 模式,只接受通过 init_data.py 声明的菜单 / 按钮,Web UI 手动创建的会在下次启动时被删除。
某子树需允许动态创建菜单时,不要对它调 reconcile_menu_subtree。
启动日志报 ensure_role 'XXX': missing apis [...]
声明的 (method, path) 在 Api 表里找不到对应路由——通常因为:
- 路由被重命名 / 删除,但角色 seed 的
apis列表忘了同步 - 拼写错误(注意 method 是小写:
"post"不是"POST")
看到必须修——否则该角色的对应权限会静默缺失。详见 启动初始化与对账。
删了 init_data 里的角色,DB 中的 Role 还在
ensure_role 是 upsert,不会自动删 init_data 中已删除的角色。删除走数据库迁移:
# migrations/app_system/
async def upgrade(db):
await db.execute_query("DELETE FROM roles WHERE role_code = 'R_OLD'")业务种子数据同理,刻意不自动化。详见 启动初始化与对账。
业务模块新加的,但路由没挂
启动日志会有提示。常见原因:
Business: module 'inventory' discovered but has no api.py or api/ package — routes will not be registered补 api/__init__.py 并 export router: APIRouter。
Business: module 'inventory' api module does not export a valid 'router' (APIRouter) objectapi/__init__.py 必须有 router = APIRouter() 这一行。详见 自动发现。
想临时屏蔽某个业务模块
mv app/business/inventory app/business/_inventoryautodiscover 会跳过 _ 开头的目录。
CLI 生成的代码里 // TODO
外键 / 自定义枚举的下拉数据源无法自动推导。搜全工程 TODO,补齐 options 的数据源(一般是 fetchGetXxxList 请求)。
权限
改了角色 / 菜单后用户没刷出权限
权限走 Redis 缓存。CUD 后主动刷新:
from app.core.cache import load_role_permissions, load_user_roles
await load_role_permissions(redis, role_code="R_HR_ADMIN")
await load_user_roles(redis, user_id=123)或者直接重启(启动时 refresh_all_cache 会全量加载)。
用户被踢但 token 还能用
修改密码 / 强制下线后调:
from app.system.services.auth import invalidate_user_session
await invalidate_user_session(redis, user_id)会 INCR token_version:{uid},旧 token 在下一次请求时返回 2106 SESSION_INVALIDATED。详见 认证。
业务接口权限拒绝(2201)但角色已挂菜单
菜单和 API 是两个不同的权限维度。角色 seed 必须同时给 menus 和 apis:
await ensure_role(
role_code="R_HR_ADMIN",
menus=[..., "hr_employee"],
apis=[
("post", "/api/v1/business/hr/employees/search"),
("post", "/api/v1/business/hr/employees"),
...
],
)部门主管看到了全公司的数据
种子里没有显式声明 data_scope。模型默认 all = 全可见。
{
"role_code": "R_DEPT_MGR",
"data_scope": DataScopeType.department, # ← 必须显式
...
}详见 数据权限。
路由(前端)
修改 .env 不生效
需要重启 Vite 开发服务器。
路由不在菜单中显示
检查菜单 hide_in_menu=True,或后端没把该 route_name 加到当前角色的 menus。
静态路由 vs 动态路由
VITE_AUTH_ROUTE_MODE=static— 前端自定义路由,靠rolesmeta 过滤VITE_AUTH_ROUTE_MODE=dynamic(默认) — 后端按角色下发路由,调GET /api/v1/route/user-routes
跨域错误
开发环境用 Vite 代理(vite.config.ts 的 server.proxy),生产环境用 Nginx 反代(详见 部署)。
生产 404
确保 Nginx 配置了 try_files $uri $uri/ /index.html;,否则刷新内层路由会 404。
Vue "多根元素" 报错
<Transition> 切换页面动画依赖单根元素:
<template>
<div>...</div> <!-- ✅ 单根 -->
</template>
<template>
<div>...</div>
<div>...</div> <!-- ❌ 多根 -->
</template>API ID
前端拿到的 ID 是字符串 Yc7vN3kE,不是数字
那是 sqid — 对外暴露的资源 ID 一律走 sqid。前端无需解码,原样发回后端即可(后端用 SqidPath / SqidId 自动解码)。
测试里发数字 ID 也通过了?
兼容期允许:SqidId 的 _sqid_to_int 同时接受 int / 数字字符串 / sqid 三种形式。迁移完成后可在源码中收紧。
部署后所有外部 sqid 链接都失效
SECRET_KEY 被换了——sqid 字母表由 SECRET_KEY 派生。详见 Sqids / 轮换 SECRET_KEY。
部署
容器里所有请求都被 guard 误封
部署在 Nginx 之后没开反代头还原,所有请求看起来都来自 nginx 容器 IP。修复:
PROXY_HEADERS_ENABLED=true
TRUSTED_HOSTS=["10.0.0.0/8"] # 信任的上游怎么让某 IP 不被 guard 封
redis-cli --scan --pattern "fastapi_guard:*" | xargs redis-cli del或临时 GUARD_ENABLED=false 重启。
切换数据库后启动报 "module not found: asyncpg"
主库引擎没装对应驱动:
uv add asyncpg # postgres
uv add asyncmy # mysql
uv add asyncodbc # mssql详见 切换数据库 / 驱动安装。
多 worker 启动后菜单 / 用户重复创建?
不会。_run_init_data 通过 Redis 锁 app:init_lock 选 leader,只有 leader 跑 init。
redis-cli get app:init_done # 应该是 "1"Docker
查看后端日志
make logs # 全部
make logs SVC=app # 仅后端;SVC=nginx|redis 同理,TAIL=N 指定行数
docker compose logs -f app # 等价的原生写法更新部署
git pull
make down && make up容器内时区不对
Tortoise use_tz=False, timezone=Asia/Shanghai。如需 UTC 全局:在 app/core/config.py 改 TORTOISE_ORM["use_tz"]=True,并把 timezone 改成 UTC。