Skip to content
Draft
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
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
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ repos:
- id: check-toml

- repo: https://github.com/tombi-toml/tombi-pre-commit
rev: v0.9.2
rev: v0.7.28
hooks:
- id: tombi-lint
args: ["--offline"]
- id: tombi-format
args: ["--offline"]

- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.15.5
rev: v0.15.1
hooks:
- id: ruff-check
args:
Expand All @@ -29,7 +29,7 @@ repos:
- id: ruff-format

- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.10.9
rev: 0.10.2
hooks:
- id: uv-lock
- id: uv-export
Expand Down
4 changes: 2 additions & 2 deletions backend/app/admin/api/v1/auth/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ async def get_codes(db: CurrentSession, request: Request) -> ResponseSchemaModel


@router.post('/refresh', summary='刷新 token')
async def refresh_token(db: CurrentSession, request: Request) -> ResponseSchemaModel[GetNewToken]:
data = await auth_service.refresh_token(db=db, request=request)
async def refresh_token(db: CurrentSession, request: Request, response: Response) -> ResponseSchemaModel[GetNewToken]:
data = await auth_service.refresh_token(db=db, request=request, response=response)
return response_base.success(data=data)


Expand Down
2 changes: 1 addition & 1 deletion backend/app/admin/api/v1/monitor/online.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def append_token_detail() -> None:
for key in token_keys:
token = await redis_client.get(key)
token_payload = jwt_decode(token)
user_id = token_payload.id
user_id = token_payload.user_id
session_uuid = token_payload.session_uuid
token_detail = GetTokenDetail(
id=user_id,
Expand Down
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
49 changes: 37 additions & 12 deletions backend/app/admin/crud/crud_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,22 @@
UpdateUserParam,
)
from backend.app.admin.utils.password_security import get_hash_password
from backend.utils.dynamic_import import import_module_cached
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 @@ -118,6 +130,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 @@ -127,7 +141,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 @@ -141,6 +159,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 @@ -149,7 +169,8 @@ async def add_by_oauth2(self, db: AsyncSession, obj: AddOAuth2UserParam) -> None
result = await db.execute(role_stmt)
role = result.scalars().first() # 默认绑定第一个角色

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 @@ -174,7 +195,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 Expand Up @@ -298,17 +323,17 @@ async def delete(self, db: AsyncSession, user_id: int) -> int:
:param user_id: 用户 ID
:return:
"""
if check_plugin_installed('oauth2'):
try:
from backend.plugin.oauth2.crud.crud_user_social import user_social_dao

await user_social_dao.delete_by_user_id(db, user_id)
except ImportError:
raise errors.ServerError(msg='OAuth2 插件用法导入失败,请联系系统管理员')

user_role_stmt = delete(user_role).where(user_role.c.user_id == user_id)
await db.execute(user_role_stmt)

try:
user_social = import_module_cached('backend.plugin.oauth2.crud.crud_user_social')
user_social_dao = user_social.user_social_dao
except (ImportError, AttributeError):
pass
else:
await user_social_dao.delete_by_user_id(db, user_id)

return await self.delete_model(db, user_id)

async def get_join(
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
10 changes: 5 additions & 5 deletions backend/app/admin/model/opera_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,26 @@

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'

id: Mapped[id_key] = mapped_column(init=False)
trace_id: Mapped[str] = mapped_column(sa.String(32), comment='请求跟踪 ID')
username: Mapped[str | None] = mapped_column(sa.String(64), comment='用户名')
method: Mapped[str] = mapped_column(sa.String(32), comment='请求类型')
method: Mapped[str] = mapped_column(sa.String(32), comment='请求方法')
title: Mapped[str] = mapped_column(sa.String(256), comment='操作模块')
path: Mapped[str] = mapped_column(sa.String(512), comment='请求路径')
ip: Mapped[str] = mapped_column(sa.String(64), comment='IP地址')
ip: Mapped[str] = mapped_column(sa.String(64), comment='IP 地址')
country: Mapped[str | None] = mapped_column(sa.String(64), comment='国家')
region: Mapped[str | None] = mapped_column(sa.String(64), comment='地区')
city: Mapped[str | None] = mapped_column(sa.String(64), comment='城市')
user_agent: Mapped[str | None] = mapped_column(sa.String(512), comment='请求头')
user_agent: Mapped[str | None] = mapped_column(sa.String(512), comment='用户代理')
os: Mapped[str | None] = mapped_column(sa.String(64), comment='操作系统')
browser: Mapped[str | None] = mapped_column(sa.String(64), comment='浏览器')
device: Mapped[str | None] = mapped_column(sa.String(64), comment='设备')
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
Loading