from __future__ import annotations from dataclasses import dataclass from collections.abc import Iterable from typing import Any from sqlalchemy import select from iti.applications.common.enums import GenderEnum, MenuTypeEnum, StatusEnum, SysConfigType from iti.applications.extensions import db from iti_system.models import Role, SysConfig, SysDictData, SysDictType, SysMenu, User from iti.modules import ModuleMenuSeed from iti.modules.registry import ModuleRegistry @dataclass class SeedCounter: created: int = 0 updated: int = 0 skipped: int = 0 def as_dict(self) -> dict[str, int]: return { "created": self.created, "updated": self.updated, "skipped": self.skipped, } SYSTEM_MENU_ID = "b3af308711954e62ba7471891b82f721" USER_MENU_ID = "93e1c7448c144f60875c4725dfa93b5a" ROLE_MENU_ID = "8ac71de8a14c413997f7f81f5fcf343c" DEPT_MENU_ID = "5d76b276594349b5bfbed42da656bd53" MENU_MENU_ID = "b3f689cf6d594f94aeed912bd5c7dd80" CONFIG_MENU_ID = "42a830108d9c49bca4e0108aba27a3cf" DICT_MENU_ID = "50d583e8c8584d43ab94939420dce0cb" LOG_MENU_ID = "774da56753514b4eafc913162970f4f1" DEFAULT_ROLES = [ { "name": "管理员", "code": "ADMIN", "desc": "系统默认管理员", "sort": 0, }, { "name": "普通角色", "code": "COMMON", "desc": "一般角色", "sort": 100, }, ] DEFAULT_USERS = [ { "username": "admin", "password": "123456", "realname": "管理员", "phone": "18888888888", "email": "a@a.com", "avatar": "", "gender": GenderEnum.SECURE.value, "desc": "系统默认管理员", "role_codes": ["ADMIN"], }, ] DEFAULT_MENUS = [ { "id": SYSTEM_MENU_ID, "name": "System", "type": MenuTypeEnum.CATALOG.value, "path": "/system", "sort": 0, "meta": {"title": "系统管理", "icon": "ion:settings-outline"}, }, { "id": USER_MENU_ID, "name": "User", "type": MenuTypeEnum.MENU.value, "path": "/system/user", "component": "/system/user/list", "sort": 1, "parent_id": SYSTEM_MENU_ID, "auth_code": "system:user:list", "meta": {"title": "system.user.title", "icon": "carbon:user"}, }, { "id": ROLE_MENU_ID, "name": "Role", "type": MenuTypeEnum.MENU.value, "path": "/system/role", "component": "/system/role/list", "sort": 2, "parent_id": SYSTEM_MENU_ID, "auth_code": "system:role:list", "meta": {"title": "角色管理", "icon": "carbon:user-role"}, }, { "id": DEPT_MENU_ID, "name": "Dept", "type": MenuTypeEnum.MENU.value, "path": "/system/dept", "component": "/system/dept/list", "sort": 3, "parent_id": SYSTEM_MENU_ID, "auth_code": "system:dept:list", "meta": {"title": "部门管理", "icon": "carbon:container-services", "order": 3}, }, { "id": MENU_MENU_ID, "name": "Menu", "type": MenuTypeEnum.MENU.value, "path": "/system/menu", "component": "/system/menu/list", "sort": 4, "parent_id": SYSTEM_MENU_ID, "auth_code": "system:menu:list", "meta": {"title": "菜单管理", "icon": "carbon:menu", "badge": ""}, }, { "id": CONFIG_MENU_ID, "name": "SysConfig", "type": MenuTypeEnum.MENU.value, "path": "/system/config", "component": "/system/config/list", "sort": 5, "parent_id": SYSTEM_MENU_ID, "auth_code": "system:config:list", "meta": {"title": "系统配置", "icon": "carbon:document-configuration", "order": 5}, }, { "id": DICT_MENU_ID, "name": "SysDict", "type": MenuTypeEnum.MENU.value, "path": "/system/dict", "component": "/system/dict/list", "sort": 6, "parent_id": SYSTEM_MENU_ID, "auth_code": "system:dict:list", "meta": {"title": "字典管理", "icon": "carbon:book"}, }, { "id": LOG_MENU_ID, "name": "SysLog", "type": MenuTypeEnum.MENU.value, "path": "/system/log", "component": "/system/log/list", "sort": 7, "parent_id": SYSTEM_MENU_ID, "auth_code": "system:log:list", "meta": {"title": "日志管理", "icon": "carbon:cloud-logging"}, }, { "id": "c5d1be767bce409bafa141ec8e5fc419", "name": "SystemUserCreate", "type": MenuTypeEnum.BUTTON.value, "path": "", "sort": 0, "parent_id": USER_MENU_ID, "auth_code": "system:user:create", "meta": {"title": "common.create", "order": 0}, }, { "id": "899b280630334766b01ff83f6f4ebacc", "name": "SystemUserUpdate", "type": MenuTypeEnum.BUTTON.value, "path": "", "sort": 1, "parent_id": USER_MENU_ID, "auth_code": "system:user:edit", "meta": {"title": "common.edit"}, }, { "id": "2431b03462a84f90ba15c29bca07d39e", "name": "SystemUserDelete", "type": MenuTypeEnum.BUTTON.value, "path": "", "sort": 2, "parent_id": USER_MENU_ID, "auth_code": "system:user:delete", "meta": {"title": "common.delete"}, }, { "id": "12cf74c410d044d986840c93fb70c397", "name": "SystemUserResetPassword", "type": MenuTypeEnum.BUTTON.value, "path": "", "sort": 3, "parent_id": USER_MENU_ID, "auth_code": "system:user:resetpwd", "meta": {"title": "修改密码", "order": 3}, }, { "id": "9f71686949c7410ea032956c52252a06", "name": "SystemRoleCreate", "type": MenuTypeEnum.BUTTON.value, "path": "", "sort": 0, "parent_id": ROLE_MENU_ID, "auth_code": "system:role:create", "meta": {"title": "common.create", "order": 0}, }, { "id": "bb271c537c604ee894a0339ced1b4d46", "name": "SystemRoleEdit", "type": MenuTypeEnum.BUTTON.value, "path": "", "sort": 1, "parent_id": ROLE_MENU_ID, "auth_code": "system:role:edit", "meta": {"title": "common.edit", "order": 1}, }, { "id": "a643f56b9a844c1eb8af7da1bd8e96da", "name": "SystemRoleDelete", "type": MenuTypeEnum.BUTTON.value, "path": "", "sort": 2, "parent_id": ROLE_MENU_ID, "auth_code": "system:role:delete", "meta": {"title": "common.delete", "order": 2}, }, { "id": "7f2913a04e1b47c8bc590eee4708a147", "name": "SystemDeptCreate", "type": MenuTypeEnum.BUTTON.value, "path": "", "sort": 0, "parent_id": DEPT_MENU_ID, "auth_code": "system:dept:create", "meta": {"title": "common.create", "order": 0}, }, { "id": "3e6e8ebda98c4e1aad27478d7bac595a", "name": "SystemDeptEdit", "type": MenuTypeEnum.BUTTON.value, "path": "", "sort": 1, "parent_id": DEPT_MENU_ID, "auth_code": "system:dept:edit", "meta": {"title": "common.edit", "order": 1}, }, { "id": "b52bb4045a434253ab0af21d03603458", "name": "SystemDeptDelete", "type": MenuTypeEnum.BUTTON.value, "path": "", "sort": 2, "parent_id": DEPT_MENU_ID, "auth_code": "system:dept:delete", "meta": {"title": "common.delete", "order": 2}, }, { "id": "18a8a2fcc8704c94baf47590dd7eb2e8", "name": "SystemMenuCreate", "type": MenuTypeEnum.BUTTON.value, "path": None, "sort": 0, "parent_id": MENU_MENU_ID, "auth_code": "system:menu:create", "meta": {"title": "common.create"}, }, { "id": "b41da6285e3f4bc4a39aa8ae13944b41", "name": "SystemMenuEdit", "type": MenuTypeEnum.BUTTON.value, "path": "", "sort": 1, "parent_id": MENU_MENU_ID, "auth_code": "system:menu:edit", "meta": {"title": "common.edit", "order": 1}, }, { "id": "a322ddada8ed4bcca1f5edcd9f3d33d0", "name": "SystemMenuDelete", "type": MenuTypeEnum.BUTTON.value, "path": "", "sort": 2, "parent_id": MENU_MENU_ID, "auth_code": "system:menu:delete", "meta": {"title": "common.delete"}, }, { "id": "57daf457de6546ab9e887233892e712e", "name": "SystemConfigCreate", "type": MenuTypeEnum.BUTTON.value, "path": "", "sort": 0, "parent_id": CONFIG_MENU_ID, "auth_code": "system:config:create", "meta": {"title": "common.create", "order": 0}, }, { "id": "5dc4a3d79f20496d82bc85f6eed1389b", "name": "SystemConfigEdit", "type": MenuTypeEnum.BUTTON.value, "path": "", "sort": 1, "parent_id": CONFIG_MENU_ID, "auth_code": "system:config:edit", "meta": {"title": "common.edit", "order": 1}, }, { "id": "bafe03a1da6c4224b69ecadd721cba0a", "name": "SystemConfigDelete", "type": MenuTypeEnum.BUTTON.value, "path": "", "sort": 2, "parent_id": CONFIG_MENU_ID, "auth_code": "system:config:delete", "meta": {"title": "common.delete", "order": 2}, }, { "id": "8c96ce0db0724139a9ab7fbcfcf52500", "name": "SystemDictCreate", "type": MenuTypeEnum.BUTTON.value, "path": "", "sort": 0, "parent_id": DICT_MENU_ID, "auth_code": "system:dict:create", "meta": {"title": "common.create", "order": 0}, }, { "id": "de5bcbdb81b34b02991614c13f12043a", "name": "SystemDictEdit", "type": MenuTypeEnum.BUTTON.value, "path": "", "sort": 1, "parent_id": DICT_MENU_ID, "auth_code": "system:dict:edit", "meta": {"title": "common.edit", "order": 1}, }, { "id": "3385698f84e44249ad5eb733d7232c96", "name": "SystemDictDelete", "type": MenuTypeEnum.BUTTON.value, "path": "", "sort": 2, "parent_id": DICT_MENU_ID, "auth_code": "system:dict:delete", "meta": {"title": "common.delete", "order": 2}, }, { "id": "74e4cff0de2b4532a187ed01714d6577", "name": "SystemLogDelete", "type": MenuTypeEnum.BUTTON.value, "path": "", "sort": 0, "parent_id": LOG_MENU_ID, "auth_code": "system:log:delete", "meta": {"title": "common.delete", "order": 0}, }, ] DEFAULT_DICT_TYPES = [ { "type_name": "状态", "type_code": "status", "desc": "通用启停状态", "sort": 1, "data": [ {"label": "启用", "code": "enabled", "value": "enabled", "sort": 1}, {"label": "停用", "code": "disabled", "value": "disabled", "sort": 2}, ], }, { "type_name": "性别", "type_code": "gender", "desc": "用户性别", "sort": 2, "data": [ {"label": "男", "code": "male", "value": "male", "sort": 1}, {"label": "女", "code": "female", "value": "female", "sort": 2}, {"label": "保密", "code": "secure", "value": "secure", "sort": 3}, ], }, ] DEFAULT_CONFIGS = [ { "type": SysConfigType.USER.value, "name": "默认用户密码", "code": "DEFAULT_USER_PASSWORD", "value": "123456", "desc": "系统自动注册时使用的默认用户密码", "sort": 1, }, { "type": SysConfigType.USER.value, "name": "默认用户角色", "code": "DEFAULT_USER_ROLES", "value": "COMMON", "desc": "系统自动注册时使用的默认用户角色,多个角色用逗号分隔", "sort": 2, }, { "type": SysConfigType.USER.value, "name": "默认用户部门", "code": "DEFAULT_USER_DEPTS", "value": "", "desc": "系统自动注册时使用的默认用户部门,多个部门用逗号分隔", "sort": 3, }, { "type": SysConfigType.SYSTEM.value, "name": "系统名称", "code": "system.name", "value": "iTi-Flask", "desc": "默认系统名称", "sort": 10, }, { "type": SysConfigType.SYSTEM.value, "name": "后端访问地址", "code": "BACKEND_URL", "value": "http://localhost:5000", "desc": "后端访问地址。应配置为前端可访问的后端地址", "sort": 90, }, { "type": SysConfigType.SYSTEM.value, "name": "文件回收站功能", "code": "FILE_RECYCLE_ENABLED", "value": "true", "desc": "是否启用文件回收站功能", "sort": 100, }, { "type": SysConfigType.SYSTEM.value, "name": "回收站保留天数", "code": "FILE_RECYCLE_DAYS", "value": "30", "desc": "回收站文件保留天数", "sort": 101, }, { "type": SysConfigType.SYSTEM.value, "name": "文件分享功能", "code": "FILE_SHARE_ENABLED", "value": "true", "desc": "是否启用文件分享功能", "sort": 102, }, { "type": SysConfigType.SYSTEM.value, "name": "分享默认过期时间", "code": "FILE_SHARE_DEFAULT_EXPIRE_HOURS", "value": "168", "desc": "文件分享默认过期时间(小时)", "sort": 103, }, { "type": SysConfigType.SYSTEM.value, "name": "分片上传阈值", "code": "FILE_CHUNK_THRESHOLD", "value": "104857600", "desc": "文件大小超过此阈值时使用分片上传", "sort": 104, }, { "type": SysConfigType.SYSTEM.value, "name": "分片上传分片大小", "code": "FILE_CHUNK_SIZE", "value": "2097152", "desc": "分片上传时每个分片的大小(字节)", "sort": 105, }, ] ROLE_MENU_BINDINGS = { "ADMIN": [item["id"] for item in DEFAULT_MENUS], "COMMON": [ SYSTEM_MENU_ID, USER_MENU_ID, ROLE_MENU_ID, DEPT_MENU_ID, MENU_MENU_ID, CONFIG_MENU_ID, DICT_MENU_ID, ], } def seed_system_data( module_registry: ModuleRegistry | None = None, ) -> dict[str, dict[str, int]]: counters = { "roles": _seed_roles(), "menus": _seed_menus(), "module_menus": _seed_module_menus(module_registry), "dicts": _seed_dicts(), "configs": _seed_configs(), "users": _seed_users(), "user_roles": _seed_user_roles(), "role_menus": _seed_role_menus(), "module_role_menus": _seed_module_role_menus(module_registry), } db.session.commit() return {name: counter.as_dict() for name, counter in counters.items()} def _seed_roles() -> SeedCounter: counter = SeedCounter() for item in DEFAULT_ROLES: role = db.session.scalar(select(Role).filter_by(code=item["code"])) payload = dict(item) payload.setdefault("status", StatusEnum.ENABLED.value) if role is None: db.session.add(Role(**_audit_safe(payload))) counter.created += 1 continue changed = _assign_if_changed(role, payload) counter.updated += 1 if changed else 0 counter.skipped += 0 if changed else 1 return counter def _seed_menus() -> SeedCounter: counter = SeedCounter() for item in DEFAULT_MENUS: _seed_menu_payload(counter, item) return counter def _seed_module_menus(module_registry: ModuleRegistry | None) -> SeedCounter: counter = SeedCounter() for menu_seed in _module_menu_seeds(module_registry): _seed_menu_payload(counter, menu_seed.as_menu_payload()) return counter def _seed_menu_payload(counter: SeedCounter, item: dict[str, Any]) -> None: menu = db.session.scalar(select(SysMenu).filter_by(id=item["id"])) payload = dict(item) payload.setdefault("status", StatusEnum.ENABLED.value) if menu is None: db.session.add(SysMenu(**payload)) counter.created += 1 return changed = _assign_if_changed(menu, payload) counter.updated += 1 if changed else 0 counter.skipped += 0 if changed else 1 def _seed_dicts() -> SeedCounter: counter = SeedCounter() for item in DEFAULT_DICT_TYPES: data_items = item["data"] if not isinstance(data_items, Iterable): data_items = [] dict_type = db.session.scalar( select(SysDictType).filter_by(type_code=item["type_code"]) ) payload = {key: value for key, value in item.items() if key != "data"} payload.setdefault("status", StatusEnum.ENABLED.value) if dict_type is None: db.session.add(SysDictType(**_audit_safe(payload))) counter.created += 1 else: changed = _assign_if_changed(dict_type, payload) counter.updated += 1 if changed else 0 counter.skipped += 0 if changed else 1 for data in data_items: dict_data = db.session.scalar( select(SysDictData).filter_by( type_code=item["type_code"], code=data["code"] ) ) data_payload = dict(data) data_payload["type_code"] = item["type_code"] data_payload.setdefault("status", StatusEnum.ENABLED.value) if dict_data is None: db.session.add(SysDictData(**_audit_safe(data_payload))) counter.created += 1 else: changed = _assign_if_changed(dict_data, data_payload) counter.updated += 1 if changed else 0 counter.skipped += 0 if changed else 1 return counter def _seed_configs() -> SeedCounter: counter = SeedCounter() for item in DEFAULT_CONFIGS: config = db.session.scalar( select(SysConfig).filter_by(type=item["type"], code=item["code"]) ) payload = dict(item) payload.setdefault("status", StatusEnum.ENABLED.value) if config is None: db.session.add(SysConfig(**_audit_safe(payload))) counter.created += 1 continue changed = _assign_if_changed(config, payload) counter.updated += 1 if changed else 0 counter.skipped += 0 if changed else 1 return counter def _seed_users() -> SeedCounter: counter = SeedCounter() for item in DEFAULT_USERS: user = db.session.scalar(select(User).filter_by(username=item["username"])) payload = {key: value for key, value in item.items() if key != "role_codes"} if user is None: user = User(status=StatusEnum.ENABLED.value, **_audit_safe(payload)) db.session.add(user) counter.created += 1 continue counter.skipped += 1 return counter def _seed_user_roles() -> SeedCounter: counter = SeedCounter() for item in _default_user_role_bindings(): user = db.session.scalar(select(User).filter_by(username=item["username"])) if user is None: counter.skipped += len(item["role_codes"]) continue existing_codes = {role.code for role in user.roles} for role_code in item["role_codes"]: if role_code in existing_codes: counter.skipped += 1 continue role = db.session.scalar(select(Role).filter_by(code=role_code)) if role is None: counter.skipped += 1 continue user.roles.append(role) counter.created += 1 return counter def _seed_role_menus() -> SeedCounter: counter = SeedCounter() for role_code, menu_ids in _default_role_menu_bindings(): role = db.session.scalar(select(Role).filter_by(code=role_code)) if role is None: counter.skipped += len(menu_ids) continue existing_ids = {menu.id for menu in role.menus} for menu_id in menu_ids: if menu_id in existing_ids: counter.skipped += 1 continue menu = db.session.scalar(select(SysMenu).filter_by(id=menu_id)) if menu is None: counter.skipped += 1 continue role.menus.append(menu) counter.created += 1 return counter def _default_user_role_bindings() -> list[dict[str, Any]]: return [ {"username": item["username"], "role_codes": item["role_codes"]} for item in DEFAULT_USERS ] def _default_role_menu_bindings() -> list[tuple[str, list[str]]]: return list(ROLE_MENU_BINDINGS.items()) def _seed_module_role_menus(module_registry: ModuleRegistry | None) -> SeedCounter: counter = SeedCounter() for menu_seed, role_code in _module_role_menu_bindings(module_registry): role = db.session.scalar(select(Role).filter_by(code=role_code)) menu = db.session.scalar(select(SysMenu).filter_by(id=menu_seed.id)) if role is None or menu is None: counter.skipped += 1 continue if menu.id in {item.id for item in role.menus}: counter.skipped += 1 continue role.menus.append(menu) counter.created += 1 return counter def _module_role_menu_bindings( module_registry: ModuleRegistry | None, ) -> list[tuple[ModuleMenuSeed, str]]: return [ (menu_seed, role_code) for menu_seed in _module_menu_seeds(module_registry) for role_code in menu_seed.admin_roles ] def _module_menu_seeds( module_registry: ModuleRegistry | None, ) -> list[ModuleMenuSeed]: if module_registry is None: return [] return module_registry.list_menu_seeds() def _assign_if_changed(model: Any, values: dict) -> bool: changed = False for key, value in values.items(): if getattr(model, key) != value: setattr(model, key, value) changed = True return changed def _audit_safe(values: dict) -> dict: payload = dict(values) payload.setdefault("created_by", None) payload.setdefault("updated_by", None) return payload