Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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