magic-dash-pro适合构建多页面、持续扩展的管理型Dash应用,支持复杂用户与部门关系、用户名密码登录、邮件验证码登录、OTP动态口令登录、用户管理、部门管理、权限管理、登录日志、鉴权和数据库持久化能力。开发时需要同时关注页面、权限和数据模型。
- 在
views/core_pages/下新增页面文件,例如:
views/core_pages/report.py
- 编写页面渲染函数:
def render():
return ...-
在
configs/router_config.py的core_side_menu中加入菜单项。 -
在
valid_pathnames中登记pathname:
"/core/report": "报表页"- 为新页面配置访问规则。
如果新页面属于随代码发布的内置权限基线,可在configs/auth_config.py中按角色调整pathname_access_rules。如果希望运行后由管理员维护,可在“权限管理”界面中调整数据库权限组规则。某个角色使用include模式时,需要把新页面菜单key加入该角色的keys;使用exclude模式时,需要确认该页面是否应被排除。
如果希望管理员后续可在“权限管理”界面中为数据库权限组勾选该页面,请确保该页面以字符串pathname登记在RouterConfig.valid_pathnames中,且没有被加入RouterConfig.public_pathnames。权限管理界面只会列出这类受保护的字符串页面;正则通配页面仍建议通过AuthConfig.pathname_access_rules硬编码维护。
- 在
components/page_content.py中导入新页面模块,并在render()函数中补充分发逻辑:
from views.core_pages import report
...
elif pathname == "/core/report":
page_content = report.render()如需回调,在callbacks/core_pages_c/下新增对应回调文件。可以参考page1.py和login_logs.py的写法,在页面模块中导入对应回调模块,或在包初始化逻辑中集中导入,确保回调注册生效。
公开页面不需要登录即可访问,适合登录页、错误页、开放分享页等。
处理方式:
- 在
valid_pathnames中登记页面。 - 在
public_pathnames中加入页面pathname。 - 如果页面不需要核心布局,将
pathname加入independent_core_pathnames。 - 在
app.py的根路由root_router()中补充该公开路径对应的渲染分支;否则即使路径被标记为公开,也不会自动知道应渲染哪个页面。 - 如果该公开页面需要独立渲染,还要在
views/core_pages/__init__.py或对应的公开页面渲染入口中补充实际页面返回逻辑。
通配页面适合详情页这类路径中包含动态参数的页面,例如/core/report/detail/123。
处理方式:
- 在
RouterConfig.wildcard_patterns中定义正则。 - 将正则对象加入
RouterConfig.valid_pathnames。 - 如页面需要独立渲染,将正则对象加入
RouterConfig.independent_core_pathnames,并在views/core_pages/__init__.py中补充命中该正则后的页面渲染分支。 - 在页面渲染函数中从
URL中解析动态参数。 - 如果页面需要受角色权限控制,在
AuthConfig.pathname_access_rules中按角色维护对应正则对象或页面入口权限。
当前模板内置流程主要演示“独立通配页面”。如果希望新增嵌入核心布局的普通通配页面,还需要同步调整callbacks/core_pages_c/__init__.py中的路径合法性判断和多标签页标题生成逻辑,并在components/page_content.py中补充对应渲染分发。
模板支持两种角色来源:
- 硬编码角色:写在
configs/auth_config.py中,适合系统内置、需要随代码版本管理的基础角色。 - 数据库权限组:写入
models/user_permission_groups.py对应的数据表,适合运行后由管理员在“权限管理”中维护的业务角色。
实际运行时不要直接把AuthConfig.roles视为完整角色清单,应优先通过UserPermissionGroups.get_effective_roles()、get_effective_role_options()和get_effective_pathname_access_rule()读取综合后的有效角色与权限规则。
如需新增硬编码角色,在configs/auth_config.py中扩展roles:
roles = {
"admin": {"description": "系统管理员"},
"normal": {"description": "常规用户"},
"auditor": {"description": "审计人员"},
}然后在pathname_access_rules中为新角色补充访问规则:
"auditor": {
"type": "include",
"keys": ["/core/login-logs"],
}新增硬编码角色后,通常还需要运行python -m magic_init确保权限分组表存在,再在“系统管理-用户管理”中为用户分配该角色。
如需由管理员在线维护角色,可登录管理员账号,从右上角用户菜单进入“权限管理”,使用“新增权限组”写入数据库权限组。数据库权限组的id会直接作为Users.user_role保存,名称用于页面展示,访问规则用于侧边菜单、页面搜索和直接访问校验。
数据库权限组支持以下访问规则:
| 规则 | 行为 |
|---|---|
all |
可访问全部受保护页面 |
include |
仅可访问勾选的页面;首页仍自动放行 |
exclude |
可访问除勾选页面外的其他受保护页面 |
需要注意:
admin权限组只能通过AuthConfig硬编码定义,不能新增、编辑、删除或覆盖;管理员角色必须同时在AuthConfig.roles和AuthConfig.pathname_access_rules中保留明确规则,不能依赖数据库权限组补充管理员权限。- 编辑非管理员硬编码角色时,会新增或更新一条同
id数据库记录,用数据库内容覆盖该角色的名称和访问规则;删除这条数据库记录即可恢复硬编码配置。 - 新增数据库权限组时,
id和名称不能与任何有效角色重复,也不能与硬编码角色名称冲突。 - 删除数据库权限组前,如果该权限组已被用户引用,需要先在用户管理中把相关用户调整到其他有效角色。
- 如果某个用户保存了已失效的角色,访问受保护页面会进入
403,用户管理列表会以异常状态展示该角色。
页面访问权限的实际生效链路如下:
magic_init.py创建或升级UserPermissionGroups数据表。UserPermissionGroups.get_effective_roles()合成硬编码角色和数据库权限组,供用户管理下拉框、用户列表和页首角色展示使用。UserPermissionGroups.get_effective_pathname_access_rule()为当前用户角色计算最终页面访问规则。app.py的根路由先检查登录状态,再按最终规则拦截受保护页面的直接访问。views/core_pages/__init__.py和components/core_side_menu.py基于同一规则过滤页面搜索选项和侧边菜单。
Flask后端仍保留flask-principal相关兼容能力,并通过懒加载的user_permissions支持数据库角色;FastAPI后端不依赖flask-principal,但页面权限校验、管理界面和数据模型与Flask版本保持一致。
| 文件 | 作用 |
|---|---|
configs/auth_config.py |
定义硬编码角色和默认页面访问规则 |
models/user_permission_groups.py |
合成有效角色、校验权限规则、维护数据库权限组 |
components/permission_manage.py |
管理员权限管理抽屉,支持查看、新增、编辑和删除权限组 |
components/user_manage.py |
使用有效角色作为用户新增和编辑时的角色选项 |
components/core_side_menu.py |
按当前用户有效访问规则过滤侧边菜单 |
views/core_pages/__init__.py |
按当前用户有效访问规则过滤页面搜索并注入权限管理入口 |
callbacks/core_pages_c/__init__.py |
处理右上角用户菜单中的“权限管理”入口点击事件 |
app.py |
对受保护页面进行登录状态和页面访问规则校验 |
magic_init.py |
创建内置权限分组表并展示创建记录 |
数据库模型放在models/目录。建议参考已有的users.py、departments.py、logs.py组织模型字段和操作方法。
开发流程:
- 在
models/下新增模型文件。 - 在
magic_init.py的初始化表清单或自定义初始化逻辑中注册新表。models/init_db.py已废弃,仅保留为迁移提示入口;内置的用户权限分组表已在当前模板中注册。 - 运行
python -m magic_init初始化或更新基础数据。 - 在页面回调中调用模型方法读写数据。
切换数据库类型时,先修改DatabaseConfig.database_type和对应连接配置,再确认目标数据库已创建。
内置模型会在Peewee、SQLAlchemy和SQLModel实现之间保持一致的上层类型契约。以登录日志为例,LoginLogs.add_log()的login_datetime参数应传入原生datetime对象,LoginLogs.get_logs()返回记录中的login_datetime也始终为datetime对象。日期时间的字符串格式化应留在页面展示、文件导出或接口序列化层处理,不应放在模型层,以免不同ORM返回不同类型。
常见安全增强点:
- 开启
enable_login_captcha增加登录滑块验证。 - 开启
enable_email_login增加邮件验证码登录方式。 - 开启
enable_otp_login增加OTP动态口令登录方式。 - 开启
enable_duplicate_login_check辅助发现重复登录。 - 开启
enable_fullscreen_watermark为全屏场景增加用户水印。 - 开启
enable_login_rsa_crypto后,运行python -m magic_init生成RSA密钥对。
管理员可在“系统管理-用户管理”中新增、删除和编辑非管理员用户。新增用户时可填写用户名、密码、邮箱、所属部门和用户角色;编辑用户时,用户id和用户名保持只读,可调整邮箱、所属部门和用户角色。
用户角色选项来自硬编码配置与数据库权限组综合后的有效角色。用户邮箱允许为空,但非空邮箱必须唯一,主要用于邮件验证码登录;所属部门可以为空,选择部门时会校验目标部门是否仍然存在。为避免误操作,模板默认不允许在用户管理中编辑或删除管理员角色用户。
邮件验证码登录在Flask和FastAPI后端变体中的配置和交互一致,默认处于关闭状态。完整启用流程如下:
- 在
configs/base_config.py中设置BaseConfig.enable_email_login = True。 - 按照配置参数填写
configs/email_config.py中的SMTP参数。 - 运行
python -m magic_init,确保用户表邮箱字段、邮箱唯一约束和邮件验证码表已经创建。 - 通过初始化提示或“系统管理-用户管理”为用户关联邮箱。
- 启动应用,从登录页“更多登录方式-邮箱验证登录”完成一次发送和登录测试。
用户邮箱允许为空,但所有非空邮箱必须唯一。只有已关联有效用户的邮箱才能获取验证码。初始化新数据库时,magic_init.py会询问可选的初始管理员邮箱;升级已有项目时,该命令会自动补充user_email字段、唯一索引和邮件验证码表。旧数据库中已有的failed_attempts列可以保留,升级后不再读写该列。若旧数据中存在重复的非空邮箱,需要先清理重复值,才能成功建立唯一约束。
- 验证码是由安全随机数生成器产生的6位数字。
verification_code_expire_seconds只控制验证码有效期,默认300秒。verification_code_resend_interval_seconds单独控制同一邮箱的重复发送等待,默认60秒,且不能超过验证码有效期。- 重复发送等待结束后可以获取新验证码;新验证码签发成功后旧验证码立即失效,并重新计算有效期。
- 模板不累计验证码错误次数,输入错误不会锁定当前验证码;验证码仍受有效期和成功后单次消费约束。
- 验证成功后验证码会立即消费;过期记录会在校验时清理。首次邮件发送失败会清理本次签发记录,重发邮件失败则恢复此前仍存在的验证码记录。
- 成功或失败的验证码登录结果会写入登录日志;邮件验证码登录会建立普通非“记住我”会话。
enable_login_captcha只作用于用户名密码登录表单,不会自动应用到邮件验证码登录弹窗。模板当前按邮箱维度限制重复发送,不累计单个验证码的错误次数,也不包含按IP或全局维度的发送、校验限流;面向公网部署时,建议在反向代理、网关或邮件发送服务侧补充限流与监控。
| 文件 | 作用 |
|---|---|
configs/base_config.py |
控制是否展示并启用邮件验证码登录 |
configs/email_config.py |
配置SMTP连接、发件人、验证码有效期和重复发送等待时间 |
models/users.py |
维护唯一的用户邮箱并按邮箱查询用户 |
models/email_verifications.py |
签发、限频、校验和消费验证码 |
utils/email_utils.py |
校验邮件配置并发送纯文本及HTML验证码邮件 |
callbacks/login_c.py |
处理验证码发送、倒计时、校验、登录和日志记录 |
magic_init.py |
创建或兼容升级用户邮箱及验证码数据表结构 |
OTP动态口令登录在Flask和FastAPI后端变体中的配置和交互一致,默认处于关闭状态。完整启用流程如下:
- 在
configs/base_config.py中设置BaseConfig.enable_otp_login = True。 - 按照配置参数检查或调整
configs/otp_config.py中的OTP参数。 - 运行
python -m magic_init,确保OTP凭据表已经创建;已初始化过的旧数据库也会在启用或绑定时兼容创建该表。 - 启动应用,用户登录后从右上角用户菜单进入“OTP绑定”,使用认证器
App扫码并输入当前6位动态口令完成绑定。 - 重新打开登录页,从“更多登录方式-OTP动态口令登录”输入用户名和认证器
App中的6位动态口令完成登录测试。
OTP登录不要求输入密码,也不支持“记住我”表单状态;它会在校验成功后建立普通登录会话。未绑定OTP的用户无法通过该方式登录。用户重复绑定时会用新的二维码和共享密钥覆盖旧绑定。
- 口令固定为6位数字,默认每30秒刷新一次。
otp_valid_window默认允许前后各1个时间窗口,适合容忍轻微时钟偏差;安全要求较高时可以调小。- 口令成功使用后会记录对应时间窗口,防止同一动态口令重复使用。
- 连续校验失败达到
max_failed_attempts后,会临时锁定该用户的OTP登录lockout_seconds秒。 - 成功、失败、锁定和口令失效等结果会写入登录日志,并在登录日志页面中以状态标签展示。
OTP共享密钥加密落库。生产环境建议设置独立的OtpConfig.secret_crypto_key,不要频繁更换;更换后旧凭据可能无法解密,需要用户重新绑定。
| 文件 | 作用 |
|---|---|
configs/base_config.py |
控制是否展示并启用OTP动态口令登录 |
configs/otp_config.py |
配置OTP发行方、时间窗口、失败锁定和密钥加密材料 |
models/otp_credentials.py |
保存用户OTP凭据、失败次数、锁定状态和已使用时间窗口 |
utils/otp_utils.py |
生成共享密钥、构造扫码地址、加解密密钥和校验动态口令 |
components/otp_binding.py |
处理用户扫码绑定和重新绑定流程 |
callbacks/login_c.py |
处理OTP登录校验、会话建立和日志记录 |
magic_init.py |
创建内置OTP凭据表并展示表创建记录 |
magic-dash-pro创建时可选择Flask或FastAPI后端。两种变体的上层页面、配置、模型和回调组织方式基本一致,主要差异集中在server.py、登录会话管理依赖和后端集成方式。
FastAPI后端变体可通过BaseConfig.enable_fastapi_docs和BaseConfig.enable_fastapi_redoc分别启用Swagger UI与ReDoc,并通过fastapi_docs_pathname和fastapi_redoc_pathname设置独立访问路径。两种文档默认关闭;BaseConfig.fastapi_docs_offline默认为False,设置为True后,已启用的两种文档会统一使用模板内置离线静态资源。详细配置示例见配置参数。BaseConfig.fastapi_docs_admin_only默认为True,启用后只有AuthConfig.admin_role对应的管理员可以访问文档页面、OAuth2 redirect及/openapi.json;未登录和非管理员请求分别返回401和403。如需公开这些端点,可将该参数设置为False。
接口文档直接注册在app.server上,不会创建第二个FastAPI实例。开发自定义接口时继续使用同一服务对象:
from server import app
@app.server.get("/api/health", tags=["系统"])
async def health_check():
return {"status": "ok"}启用接口文档并重启应用后,上述接口会自动出现在文档中。文档pathname必须是以/开头的静态路径,不能占用应用根路径、Dash内部接口、assets静态资源或其他已注册路由。server.py会在文件末尾调用utils/fastapi_docs.py,由该工具模块校验配置时已经存在的路由,并复用FastAPI原生处理函数将文档路由重新挂载到配置地址。后续注册的自定义接口仍会进入OpenAPI,但其pathname也应主动避开已配置的文档地址。
文档权限由server.py中的现有登录中间件统一处理,并根据server.docs_url、server.redoc_url、server.swagger_ui_oauth2_redirect_url和server.openapi_url识别请求,因此自定义文档pathname不需要重复配置权限规则。管理员访问前应先通过/login建立登录cookie;接口文档端点不会把匿名请求重定向到登录页,而是返回语义明确的401响应。
在线模式下,文档页面沿用FastAPI默认配置,通过外部CDN加载Swagger UI和ReDoc资源。离线模式下,资源由Dash现有的assets静态文件路由提供;assets/fastapi-docs/已从Dash页面的自动JS/CSS注入扫描中排除,因此不会增加普通页面的资源负担或污染其样式。
从Flask后端迁移到FastAPI后端时,可参考后端迁移指南。
迁移后端变体时,建议新建目标变体项目,再迁移:
magic-dash create --name magic-dash-pro --backend fastapi等价的简写形式为magic-dash create -n magic-dash-pro -b fastapi。
views/callbacks/components/- 自定义
models/ - 自定义
assets/ - 业务配置项
Dash会自动加载项目assets/目录下的静态资源。业务自定义样式、脚本、图片和图标可直接放入生成项目中的assets/目录,并使用/assets/...路径引用。
magic-dash-pro登录页默认使用以下公共静态资源:
assets/videos/login-bg.mp4
assets/imgs/login/gradient-bg.jpg
assets/imgs/login/gradient-bg-side.png
这些文件在magic-dash源码仓库中由magic_dash/public_assets/统一维护,内置magic-dash-pro和magic-dash-pro-fastapi模板目录默认不跟踪对应副本,以避免重复的大文件增大源码仓库和发布包体积。
通过magic-dash create --name magic-dash-pro创建项目时,CLI会自动把公共资源复制到新项目的assets/目录中,并在终端输出复制状态。生成项目中的这些文件属于项目静态资源,可以按业务需求替换或删除。
如果你在magic-dash源码仓库内直接开发内置模板,可使用以下命令恢复模板目录下的公共资源副本:
magic-dash init-assets提交或发布前,可使用以下命令移除模板目录下的公共资源副本:
magic-dash remove-assets