""" 种子数据脚本(幂等,可重复执行) ============================================================= 在空库上初始化系统必需数据,供多数据库(mysql/pgsql/mssql/sqlite/oracle)就绪: - admin 超管用户(id=1,密码见 DEFAULT_PASSWORD,默认 123456) - 超级管理员角色 + 关联 - 系统管理目录 + 常用管理菜单 + 查询权限节点 - 基础字典(性别)、站点配置、系统参数 用法:由 scripts/setup_db.py 在 app 上下文内调用 seed_all()。 """ import logging import os import sys # 将项目根加入 sys.path(脚本以 `python scripts/xxx.py` 运行时 cwd 是 scripts/) ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if ROOT not in sys.path: sys.path.insert(0, ROOT) from src.core.database import db # noqa: E402 from src.core.password import encrypt_password, generate_salt # noqa: E402 from src.common.constants.common import DEFAULT_PASSWORD # noqa: E402 from src.modules.system.user.repository import UserRepository # noqa: E402 from src.modules.system.role.repository import RoleRepository # noqa: E402 from src.modules.association.user_role.repository import UserRoleRepository # noqa: E402 from src.modules.system.menu.repository import MenuRepository # noqa: E402 from src.modules.dictionary.dict.repository import DictRepository # noqa: E402 from src.modules.dictionary.dict_item.repository import DictItemRepository # noqa: E402 from src.modules.configuration.config.repository import ConfigRepository # noqa: E402 from src.modules.configuration.config_item.repository import ConfigItemRepository # noqa: E402 from src.modules.param.repository import ParamRepository # noqa: E402 logger = logging.getLogger(__name__) def _get_or_create(repo, filters, defaults): """幂等:按等值条件查,不存在则创建""" obj = repo.first_by(filters) if obj is None: obj = repo.create(**defaults) db.session.commit() logger.info(f"已创建 {repo.__name__}: {filters}") return obj def seed_admin_user(): """创建 admin 超管用户(id=1,超管绕过 user_id==1)""" if UserRepository.first_by({"username": "admin"}): logger.info("admin 用户已存在,跳过") return salt = generate_salt() admin = UserRepository.create( username="admin", realname="超级管理员", gender=1, avatar="", mobile="", email="", dept_id=0, level_id=0, position_id=0, province_code="", city_code="", district_code="", street_code="", address="", status=1, sort=1, note="系统内置超管,请登录后修改密码", password=encrypt_password(DEFAULT_PASSWORD, salt), salt=salt, create_user="system", ) db.session.commit() logger.info(f"已创建 admin 用户(id={admin.id},初始密码 {DEFAULT_PASSWORD})") def seed_superadmin_role(): """创建超级管理员角色并关联 admin 用户""" role = _get_or_create( RoleRepository, {"code": "admin"}, {"name": "超级管理员", "code": "admin", "status": 1, "sort": 1, "create_user": "system"}, ) admin = UserRepository.first_by({"username": "admin"}) if admin and not UserRoleRepository.first_by({"user_id": admin.id, "role_id": role.id}): UserRoleRepository.create(user_id=admin.id, role_id=role.id, create_user="system") db.session.commit() logger.info("已关联 admin → 超级管理员") def seed_basic_menus(): """创建系统管理目录 + 常用管理菜单 + 查询权限节点""" if MenuRepository.first_by({"name": "系统管理", "parent_id": 0}): logger.info("系统管理菜单已存在,跳过") return sys_dir = MenuRepository.create( name="系统管理", parent_id=0, path="/system", component="", permission="", type=0, status=1, target=0, sort=1, icon="setting", hide=0, create_user="system", ) db.session.commit() child_menus = [ ("用户管理", "/user", "system/user/index", "sys:user:page", 1), ("角色管理", "/role", "system/role/index", "sys:role:page", 2), ("菜单管理", "/menu", "system/menu/index", "sys:menu:page", 3), ("部门管理", "/dept", "system/dept/index", "sys:dept:page", 4), ("岗位管理", "/position", "system/position/index", "sys:position:page", 5), ("职级管理", "/level", "system/level/index", "sys:level:page", 6), ] for name, path, component, permission, sort in child_menus: menu = MenuRepository.create( name=name, parent_id=sys_dir.id, path=path, component=component, permission=permission, type=0, status=1, target=0, sort=sort, hide=0, create_user="system", ) db.session.commit() # 查询权限节点(type=1,前端按钮/接口鉴权用) MenuRepository.create( name=f"查询{name}", parent_id=menu.id, path=f"{path}/page", component="", permission=permission, type=1, status=1, target=0, sort=1, create_user="system", ) db.session.commit() logger.info("已创建系统管理目录与常用菜单") def seed_import_export_permissions(): """为存量菜单幂等补齐「导入/导出」按钮权限节点 背景说明: 导入/导出 按钮在前端通过 v-perm 指令按权限码鉴权(如 sys:user:import), 权限码必须存在于菜单表中(type=1 按钮节点),否则即使后端接口已放行、 后端装饰器也已校验,前端按钮仍不会渲染。本函数为老库中已存在的菜单 补建对应按钮节点,与新建库流程(seed_basic_menus)保持一致。 设计要点: 1. 幂等:以「父菜单名 + type=0」定位父菜单,以「按钮名 + parent_id」判重, 已存在的节点直接跳过,脚本可安全重复执行。 2. 容错:父菜单缺失(如老库中未创建「职级管理」)时仅记录日志并跳过, 不中断其余按钮节点创建。 3. 按钮路径约定为 <父菜单路径>/import、<父菜单路径>/export, 与后端 router 的路由保持一致,便于前端跳转与鉴权对齐。 """ # 待补建的按钮节点配置:父菜单名 / 按钮名 / 路径 / 权限码 / 排序 btn_specs = [ ("用户管理", "导入用户", "/user/import", "sys:user:import", 2), ("用户管理", "导出用户", "/user/export", "sys:user:export", 3), ("职级管理", "导入职级", "/level/import", "sys:level:import", 2), ("职级管理", "导出职级", "/level/export", "sys:level:export", 3), ] for menu_name, btn_name, btn_path, permission, sort in btn_specs: parent = MenuRepository.first_by({"name": menu_name, "type": 0}) if not parent: logger.warning(f"菜单「{menu_name}」不存在,跳过权限节点 {permission}") continue if MenuRepository.first_by({"name": btn_name, "parent_id": parent.id}): logger.info(f"权限节点 {permission} 已存在,跳过") continue MenuRepository.create( name=btn_name, parent_id=parent.id, path=btn_path, component="", permission=permission, type=1, status=1, target=0, sort=sort, create_user="system", ) db.session.commit() logger.info(f"已创建权限节点 {permission}({btn_name})") def seed_basic_dict(): """创建性别字典""" if DictRepository.first_by({"code": "gender"}): logger.info("性别字典已存在,跳过") return dict_obj = DictRepository.create(name="性别", code="gender", sort=1, create_user="system") db.session.commit() for name, value, sort in [("男", "1", 1), ("女", "2", 2), ("保密", "3", 3)]: DictItemRepository.create( dict_id=dict_obj.id, name=name, value=value, sort=sort, create_user="system" ) db.session.commit() def seed_basic_config(): """创建站点配置""" if ConfigRepository.first_by({"code": "site_config"}): logger.info("站点配置已存在,跳过") return config = ConfigRepository.create(name="站点配置", code="site_config", sort=1, create_user="system") db.session.commit() ConfigItemRepository.create( config_id=config.id, name="网站名称", code="site_name", value="DjangoAdmin", type="text", sort=1, create_user="system", ) db.session.commit() def seed_basic_param(): """创建系统参数""" _get_or_create( ParamRepository, {"code": "system_param"}, {"name": "系统参数", "code": "system_param", "value": "", "type": 0, "status": 1, "sort": 1, "create_user": "system"}, ) def seed_all(): """执行全部种子(幂等)""" seed_admin_user() seed_superadmin_role() seed_basic_menus() # 为存量库补建导入/导出按钮权限节点,必须在菜单创建之后执行 seed_import_export_permissions() seed_basic_dict() seed_basic_config() seed_basic_param() logger.info("种子数据初始化完成")