Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f0856f0
Add tenant mode related adaptations
wu-clan Mar 8, 2026
83c3a71
Update some __table_args__
wu-clan Mar 8, 2026
74d1593
Merge branch 'master' into pre-tenant
wu-clan Mar 8, 2026
f5285a4
FIx lint and merge master
wu-clan Mar 8, 2026
ed1a164
Update tenant check
wu-clan Mar 8, 2026
87b8f6d
Merge branch 'master' into pre-tenant
wu-clan Mar 9, 2026
8014622
Fix and improve tenant-related impls
wu-clan Mar 12, 2026
c256f5c
Fix errors when the tenant is close
wu-clan Mar 12, 2026
feb2bcb
Fix token for tenant id
wu-clan Mar 13, 2026
22f1a24
Update tenant id in ctx
wu-clan Mar 14, 2026
e5b8a52
Fix known issues and improvements
wu-clan Mar 14, 2026
46ad7a5
Add OAuth2 tenant support
wu-clan Mar 14, 2026
0dd79ee
Optimize permission logic
wu-clan Mar 15, 2026
f6507c8
Add destroy SQL scripts for OAuth2 plugin
wu-clan Mar 15, 2026
4e7db61
Fix install plugin cli
wu-clan Mar 15, 2026
fbbf648
Fix lint
wu-clan Mar 15, 2026
ee00470
Fix socket auth
wu-clan Mar 15, 2026
15bea9e
Merge branch 'master' into pre-tenant
wu-clan Mar 19, 2026
96e0635
Update get_by_sid query
wu-clan Mar 19, 2026
a5cc8ac
Fix log config
wu-clan Mar 19, 2026
3a0dcb8
Fix deps and pre-commits
wu-clan Mar 19, 2026
505ae9e
Merge branch 'master' into pre-tenant
wu-clan Mar 19, 2026
6c079e4
Fix lint
wu-clan Mar 19, 2026
4a08938
Merge branch 'master' into pre-tenant
wu-clan Mar 23, 2026
05e00b2
Fix deps
wu-clan Mar 23, 2026
1f287bb
Merge branch 'master' into pre-tenant
wu-clan Apr 8, 2026
f7051d4
Merge branch 'master' into pre-tenant
wu-clan Apr 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions backend/app/admin/crud/crud_role.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,19 @@
UpdateRoleParam,
UpdateRoleScopeParam,
)
from backend.core.conf import settings
from backend.utils.serializers import select_join_serialize

if settings.TENANT_ENABLED:
try:
from backend.plugin.tenant.utils import get_tenant_dict as inject_tenant_dict
except ImportError:
raise ImportError('租户插件方法导入失败,请联系系统管理员')
else:

def inject_tenant_dict(obj: dict[str, Any]) -> dict[str, Any]:
return obj


class CRUDRole(CRUDPlus[Role]):
"""角色数据库操作类"""
Expand Down Expand Up @@ -136,9 +147,11 @@ async def update_menus(db: AsyncSession, role_id: int, menu_ids: UpdateRoleMenuP
await db.execute(role_menu_stmt)

if menu_ids.menus:
role_menu_data = [
CreateRoleMenuParam(role_id=role_id, menu_id=menu_id).model_dump() for menu_id in menu_ids.menus
]
role_menu_data = []
for menu_id in menu_ids.menus:
menu_dict = CreateRoleMenuParam(role_id=role_id, menu_id=menu_id).model_dump()
role_menu_data.append(inject_tenant_dict(menu_dict))

role_menu_stmt = insert(role_menu)
await db.execute(role_menu_stmt, role_menu_data)

Expand All @@ -158,10 +171,11 @@ async def update_scopes(db: AsyncSession, role_id: int, scope_ids: UpdateRoleSco
await db.execute(role_scope_stmt)

if scope_ids.scopes:
role_scope_data = [
CreateRoleScopeParam(role_id=role_id, data_scope_id=scope_id).model_dump()
for scope_id in scope_ids.scopes
]
role_scope_data = []
for scope_id in scope_ids.scopes:
scope_dict = CreateRoleScopeParam(role_id=role_id, data_scope_id=scope_id).model_dump()
role_scope_data.append(inject_tenant_dict(scope_dict))

role_scope_stmt = insert(role_data_scope)
await db.execute(role_scope_stmt, role_scope_data)

Expand Down
30 changes: 27 additions & 3 deletions backend/app/admin/crud/crud_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,21 @@
from backend.app.admin.utils.password_security import get_hash_password
from backend.common.enums import StatusType
from backend.common.exception import errors
from backend.core.conf import settings
from backend.plugin.core import check_plugin_installed
from backend.utils.serializers import select_join_serialize
from backend.utils.timezone import timezone

if settings.TENANT_ENABLED:
try:
from backend.plugin.tenant.utils import get_tenant_dict as inject_tenant_dict
except ImportError:
raise ImportError('租户插件方法导入失败,请联系系统管理员')
else:

def inject_tenant_dict(obj: dict[str, Any]) -> dict[str, Any]:
return obj


class CRUDUser(CRUDPlus[User]):
"""用户数据库操作类"""
Expand Down Expand Up @@ -120,6 +131,8 @@ async def add(self, db: AsyncSession, obj: AddUserParam) -> None:

dict_obj = obj.model_dump(exclude={'roles'})
dict_obj.update({'salt': salt})
dict_obj = inject_tenant_dict(dict_obj)

new_user = self.model(**dict_obj)
db.add(new_user)
await db.flush()
Expand All @@ -129,7 +142,11 @@ async def add(self, db: AsyncSession, obj: AddUserParam) -> None:
result = await db.execute(role_stmt)
roles = result.scalars().all()

user_role_data = [AddUserRoleParam(user_id=new_user.id, role_id=role.id).model_dump() for role in roles]
user_role_data = []
for role in roles:
role_dict = AddUserRoleParam(user_id=new_user.id, role_id=role.id).model_dump()
user_role_data.append(inject_tenant_dict(role_dict))

user_role_stmt = insert(user_role)
await db.execute(user_role_stmt, user_role_data)

Expand All @@ -143,6 +160,8 @@ async def add_by_oauth2(self, db: AsyncSession, obj: AddOAuth2UserParam) -> None
"""
dict_obj = obj.model_dump()
dict_obj.update({'is_staff': True, 'salt': None})
dict_obj = inject_tenant_dict(dict_obj)

new_user = self.model(**dict_obj)
db.add(new_user)
await db.flush()
Expand All @@ -153,7 +172,8 @@ async def add_by_oauth2(self, db: AsyncSession, obj: AddOAuth2UserParam) -> None
if role is None:
raise errors.NotFoundError(msg='未找到可用角色,请联系系统管理员')

user_role_stmt = insert(user_role).values(AddUserRoleParam(user_id=new_user.id, role_id=role.id).model_dump())
user_role_data = inject_tenant_dict(AddUserRoleParam(user_id=new_user.id, role_id=role.id).model_dump())
user_role_stmt = insert(user_role).values(user_role_data)
await db.execute(user_role_stmt)

async def update(self, db: AsyncSession, user_id: int, obj: UpdateUserParam) -> int:
Expand All @@ -178,7 +198,11 @@ async def update(self, db: AsyncSession, user_id: int, obj: UpdateUserParam) ->
result = await db.execute(role_stmt)
roles = result.scalars().all()

user_role_data = [AddUserRoleParam(user_id=user_id, role_id=role.id).model_dump() for role in roles]
user_role_data = []
for role in roles:
role_dict = AddUserRoleParam(user_id=user_id, role_id=role.id).model_dump()
user_role_data.append(inject_tenant_dict(role_dict))

user_role_stmt = insert(user_role)
await db.execute(user_role_stmt, user_role_data)

Expand Down
4 changes: 2 additions & 2 deletions backend/app/admin/model/dept.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

from sqlalchemy.orm import Mapped, mapped_column

from backend.common.model import Base, id_key
from backend.common.model import Base, TenantMixin, id_key


class Dept(Base):
class Dept(Base, TenantMixin):
"""部门表"""

__tablename__ = 'sys_dept'
Expand Down
4 changes: 2 additions & 2 deletions backend/app/admin/model/login_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

from sqlalchemy.orm import Mapped, mapped_column

from backend.common.model import DataClassBase, TimeZone, UniversalText, id_key
from backend.common.model import DataClassBase, TenantMixin, TimeZone, UniversalText, id_key
from backend.utils.timezone import timezone


class LoginLog(DataClassBase):
class LoginLog(DataClassBase, TenantMixin):
"""登录日志表"""

__tablename__ = 'sys_login_log'
Expand Down
11 changes: 11 additions & 0 deletions backend/app/admin/model/m2m.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import sqlalchemy as sa

from backend.common.model import MappedBase
from backend.core.conf import settings

# 租户列定义(根据配置决定是否添加)
_tenant_columns = (
(lambda: [sa.Column('tenant_id', sa.BigInteger, nullable=False, index=True, comment='租户ID')])
if settings.TENANT_ENABLED
else list
)

# 用户角色表
user_role = sa.Table(
Expand All @@ -9,6 +17,7 @@
sa.Column('id', sa.BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'),
sa.Column('user_id', sa.BigInteger, primary_key=True, comment='用户ID'),
sa.Column('role_id', sa.BigInteger, primary_key=True, comment='角色ID'),
*_tenant_columns(),
)

# 角色菜单表
Expand All @@ -18,6 +27,7 @@
sa.Column('id', sa.BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'),
sa.Column('role_id', sa.BigInteger, primary_key=True, comment='角色ID'),
sa.Column('menu_id', sa.BigInteger, primary_key=True, comment='菜单ID'),
*_tenant_columns(),
)

# 角色数据范围表
Expand All @@ -27,6 +37,7 @@
sa.Column('id', sa.BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键 ID'),
sa.Column('role_id', sa.BigInteger, primary_key=True, comment='角色 ID'),
sa.Column('data_scope_id', sa.BigInteger, primary_key=True, comment='数据范围 ID'),
*_tenant_columns(),
)

# 数据范围规则表
Expand Down
4 changes: 2 additions & 2 deletions backend/app/admin/model/opera_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

from sqlalchemy.orm import Mapped, mapped_column

from backend.common.model import DataClassBase, TimeZone, UniversalText, id_key
from backend.common.model import DataClassBase, TenantMixin, TimeZone, UniversalText, id_key
from backend.utils.timezone import timezone


class OperaLog(DataClassBase):
class OperaLog(DataClassBase, TenantMixin):
"""操作日志表"""

__tablename__ = 'sys_opera_log'
Expand Down
12 changes: 9 additions & 3 deletions backend/app/admin/model/role.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@

from sqlalchemy.orm import Mapped, mapped_column

from backend.common.model import Base, UniversalText, id_key
from backend.common.model import Base, TenantMixin, UniversalText, id_key
from backend.core.conf import settings


class Role(Base):
class Role(Base, TenantMixin):
"""角色表"""

__tablename__ = 'sys_role'

if settings.TENANT_ENABLED:
__table_args__ = (sa.UniqueConstraint('name', 'tenant_id'),)
else:
__table_args__ = (sa.UniqueConstraint('name'),)

id: Mapped[id_key] = mapped_column(init=False)
name: Mapped[str] = mapped_column(sa.String(32), unique=True, comment='角色名称')
name: Mapped[str] = mapped_column(sa.String(32), comment='角色名称')
status: Mapped[int] = mapped_column(default=1, comment='角色状态(0停用 1正常)')
is_filter_scopes: Mapped[bool] = mapped_column(default=True, comment='过滤数据权限(0否 1是)')
remark: Mapped[str | None] = mapped_column(UniversalText, default=None, comment='备注')
20 changes: 16 additions & 4 deletions backend/app/admin/model/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,35 @@

from sqlalchemy.orm import Mapped, mapped_column

from backend.common.model import Base, TimeZone, id_key
from backend.common.model import Base, TenantMixin, TimeZone, id_key
from backend.core.conf import settings
from backend.database.db import uuid4_str
from backend.utils.timezone import timezone


class User(Base):
class User(Base, TenantMixin):
"""用户表"""

__tablename__ = 'sys_user'

if settings.TENANT_ENABLED:
__table_args__ = (
sa.UniqueConstraint('username', 'tenant_id'),
sa.UniqueConstraint('email', 'tenant_id'),
)
else:
__table_args__ = (
sa.UniqueConstraint('username'),
sa.UniqueConstraint('email'),
)

id: Mapped[id_key] = mapped_column(init=False)
uuid: Mapped[str] = mapped_column(sa.String(64), init=False, default_factory=uuid4_str, unique=True)
username: Mapped[str] = mapped_column(sa.String(64), unique=True, index=True, comment='用户名')
username: Mapped[str] = mapped_column(sa.String(64), index=True, comment='用户名')
nickname: Mapped[str] = mapped_column(sa.String(64), comment='昵称')
password: Mapped[str | None] = mapped_column(sa.String(256), comment='密码')
salt: Mapped[bytes | None] = mapped_column(sa.LargeBinary(255), comment='加密盐')
email: Mapped[str | None] = mapped_column(sa.String(256), default=None, unique=True, index=True, comment='邮箱')
email: Mapped[str | None] = mapped_column(sa.String(256), default=None, index=True, comment='邮箱')
phone: Mapped[str | None] = mapped_column(sa.String(11), default=None, comment='手机号')
avatar: Mapped[str | None] = mapped_column(sa.String(256), default=None, comment='头像')
status: Mapped[int] = mapped_column(default=1, index=True, comment='用户账号状态(0停用 1正常)')
Expand Down
4 changes: 2 additions & 2 deletions backend/app/admin/model/user_password_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

from sqlalchemy.orm import Mapped, mapped_column

from backend.common.model import DataClassBase, TimeZone, id_key
from backend.common.model import DataClassBase, TenantMixin, TimeZone, id_key
from backend.utils.timezone import timezone


class UserPasswordHistory(DataClassBase):
class UserPasswordHistory(DataClassBase, TenantMixin):
"""用户密码历史记录表"""

__tablename__ = 'sys_user_password_history'
Expand Down
4 changes: 4 additions & 0 deletions backend/app/admin/schema/login_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from pydantic import ConfigDict, Field

from backend.common.schema import SchemaBase
from backend.core.conf import settings


class LoginLogSchemaBase(SchemaBase):
Expand All @@ -26,6 +27,9 @@ class LoginLogSchemaBase(SchemaBase):
class CreateLoginLogParam(LoginLogSchemaBase):
"""创建登录日志参数"""

if settings.TENANT_ENABLED:
tenant_id: int = Field(description='租户 ID')


class UpdateLoginLogParam(LoginLogSchemaBase):
"""更新登录日志参数"""
Expand Down
4 changes: 4 additions & 0 deletions backend/app/admin/schema/opera_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from backend.common.enums import StatusType
from backend.common.schema import SchemaBase
from backend.core.conf import settings


class OperaLogSchemaBase(SchemaBase):
Expand Down Expand Up @@ -34,6 +35,9 @@ class OperaLogSchemaBase(SchemaBase):
class CreateOperaLogParam(OperaLogSchemaBase):
"""创建操作日志参数"""

if settings.TENANT_ENABLED:
tenant_id: int = Field(description='租户 ID')


class UpdateOperaLogParam(OperaLogSchemaBase):
"""更新操作日志参数"""
Expand Down
7 changes: 7 additions & 0 deletions backend/app/admin/schema/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from backend.app.admin.schema.role import GetRoleWithRelationDetail
from backend.common.enums import StatusType
from backend.common.schema import CustomEmailStr, CustomPhoneNumber, SchemaBase, ser_string
from backend.core.conf import settings


class AuthSchemaBase(SchemaBase):
Expand All @@ -20,6 +21,9 @@ class AuthSchemaBase(SchemaBase):
class AuthLoginParam(AuthSchemaBase):
"""用户登录参数"""

if settings.TENANT_ENABLED:
tenant_id: int = Field(settings.TENANT_DEFAULT_ID, description='租户 ID')

uuid: str | None = Field(None, description='验证码 UUID')
captcha: str | None = Field(None, description='验证码')

Expand Down Expand Up @@ -80,6 +84,9 @@ class GetUserInfoDetail(UserInfoSchemaBase):

model_config = ConfigDict(from_attributes=True)

if settings.TENANT_ENABLED:
tenant_id: int = Field(description='租户 ID')

dept_id: int | None = Field(None, description='部门 ID')
id: int = Field(description='用户 ID')
uuid: str = Field(description='用户 UUID')
Expand Down
Loading