Skip to content
Merged
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
4 changes: 3 additions & 1 deletion backend/app/admin/api/v1/sys/dept.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ async def get_dept_tree(
phone: Annotated[str | None, Query(description='联系电话')] = None,
status: Annotated[int | None, Query(description='状态')] = None,
) -> ResponseSchemaModel[list[GetDeptTree]]:
dept = await dept_service.get_tree(db=db, request=request, name=name, leader=leader, phone=phone, status=status)
dept = await dept_service.get_tree(
db=db, request_user=request.user, name=name, leader=leader, phone=phone, status=status
)
return response_base.success(data=dept)


Expand Down
10 changes: 6 additions & 4 deletions backend/app/admin/api/v1/sys/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ async def update_user_permission(
async def update_user_password(
db: CurrentSessionTransaction, request: Request, obj: ResetPasswordParam
) -> ResponseModel:
count = await user_service.update_password(db=db, request=request, obj=obj)
count = await user_service.update_password(
db=db, user_id=request.user.id, hash_password=request.user.password, obj=obj
)
if count > 0:
return response_base.success()
return response_base.fail()
Expand All @@ -126,7 +128,7 @@ async def update_user_nickname(
request: Request,
nickname: Annotated[str, Body(embed=True, description='用户昵称')],
) -> ResponseModel:
count = await user_service.update_nickname(db=db, request=request, nickname=nickname)
count = await user_service.update_nickname(db=db, user_id=request.user.id, nickname=nickname)
if count > 0:
return response_base.success()
return response_base.fail()
Expand All @@ -138,7 +140,7 @@ async def update_user_avatar(
request: Request,
avatar: Annotated[str, Body(embed=True, description='用户头像地址')],
) -> ResponseModel:
count = await user_service.update_avatar(db=db, request=request, avatar=avatar)
count = await user_service.update_avatar(db=db, user_id=request.user.id, avatar=avatar)
if count > 0:
return response_base.success()
return response_base.fail()
Expand All @@ -151,7 +153,7 @@ async def update_user_email(
captcha: Annotated[str, Body(embed=True, description='邮箱验证码')],
email: Annotated[str, Body(embed=True, description='用户邮箱')],
) -> ResponseModel:
count = await user_service.update_email(db=db, request=request, captcha=captcha, email=email)
count = await user_service.update_email(db=db, user_id=request.user.id, captcha=captcha, email=email)
if count > 0:
return response_base.success()
return response_base.fail()
Expand Down
8 changes: 4 additions & 4 deletions backend/app/admin/crud/crud_dept.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from collections.abc import Sequence

from fastapi import Request
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy_crud_plus import CRUDPlus

from backend.app.admin.model import Dept
from backend.app.admin.schema.dept import CreateDeptParam, UpdateDeptParam
from backend.app.admin.schema.user import GetUserInfoWithRelationDetail
from backend.common.security.permission import filter_data_permission


Expand Down Expand Up @@ -34,8 +34,8 @@ async def get_by_name(self, db: AsyncSession, name: str) -> Dept | None:

async def get_all(
self,
request: Request,
db: AsyncSession,
request_user: GetUserInfoWithRelationDetail,
name: str | None,
leader: str | None,
phone: str | None,
Expand All @@ -44,8 +44,8 @@ async def get_all(
"""
获取所有部门

:param request: FastAPI 请求对象
:param db: 数据库会话
:param request_user: 请求用户
:param name: 部门名称
:param leader: 负责人
:param phone: 联系电话
Expand All @@ -63,7 +63,7 @@ async def get_all(
if status is not None:
filters['status'] = status

data_filtered = await filter_data_permission(db, request)
data_filtered = await filter_data_permission(db, request_user)
return await self.select_models_order(db, 'sort', 'desc', data_filtered, **filters)

async def create(self, db: AsyncSession, obj: CreateDeptParam) -> None:
Expand Down
8 changes: 4 additions & 4 deletions backend/app/admin/service/dept_service.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from typing import Any

from fastapi import Request
from sqlalchemy.ext.asyncio import AsyncSession

from backend.app.admin.crud.crud_dept import dept_dao
from backend.app.admin.model import Dept
from backend.app.admin.schema.dept import CreateDeptParam, UpdateDeptParam
from backend.app.admin.schema.user import GetUserInfoWithRelationDetail
from backend.common.exception import errors
from backend.core.conf import settings
from backend.database.redis import redis_client
Expand Down Expand Up @@ -34,7 +34,7 @@ async def get(*, db: AsyncSession, pk: int) -> Dept:
async def get_tree(
*,
db: AsyncSession,
request: Request,
request_user: GetUserInfoWithRelationDetail,
name: str | None,
leader: str | None,
phone: str | None,
Expand All @@ -44,15 +44,15 @@ async def get_tree(
获取部门树形结构

:param db: 数据库会话
:param request: FastAPI 请求对象
:param request_user: 请求用户
:param name: 部门名称
:param leader: 部门负责人
:param phone: 联系电话
:param status: 状态
:return:
"""

dept_select = await dept_dao.get_all(request, db, name, leader, phone, status)
dept_select = await dept_dao.get_all(db, request_user, name, leader, phone, status)
tree_data = get_tree_data(dept_select)
return tree_data

Expand Down
59 changes: 20 additions & 39 deletions backend/app/admin/service/user_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,93 +200,74 @@ async def reset_password(*, db: AsyncSession, pk: int, password: str) -> int:
return count

@staticmethod
async def update_nickname(*, db: AsyncSession, request: Request, nickname: str) -> int:
async def update_nickname(*, db: AsyncSession, user_id: int, nickname: str) -> int:
"""
更新当前用户昵称

:param db: 数据库会话
:param request: FastAPI 请求对象
:param user_id: 用户 ID
:param nickname: 用户昵称
:return:
"""
token = get_token(request)
token_payload = jwt_decode(token)
user = await user_dao.get(db, token_payload.id)
if not user:
raise errors.NotFoundError(msg='用户不存在')
count = await user_dao.update_nickname(db, token_payload.id, nickname)
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
count = await user_dao.update_nickname(db, user_id, nickname)
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user_id}')
return count

@staticmethod
async def update_avatar(*, db: AsyncSession, request: Request, avatar: str) -> int:
async def update_avatar(*, db: AsyncSession, user_id: int, avatar: str) -> int:
"""
更新当前用户头像

:param db: 数据库会话
:param request: FastAPI 请求对象
:param user_id: 用户 ID
:param avatar: 头像地址
:return:
"""
token = get_token(request)
token_payload = jwt_decode(token)
user = await user_dao.get(db, token_payload.id)
if not user:
raise errors.NotFoundError(msg='用户不存在')
count = await user_dao.update_avatar(db, token_payload.id, avatar)
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
count = await user_dao.update_avatar(db, user_id, avatar)
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user_id}')
return count

@staticmethod
async def update_email(*, db: AsyncSession, request: Request, captcha: str, email: str) -> int:
async def update_email(*, db: AsyncSession, user_id: int, captcha: str, email: str) -> int:
"""
更新当前用户邮箱

:param db: 数据库会话
:param request: FastAPI 请求对象
:param user_id: 用户 ID
:param captcha: 邮箱验证码
:param email: 邮箱
:return:
"""
token = get_token(request)
token_payload = jwt_decode(token)
user = await user_dao.get(db, token_payload.id)
if not user:
raise errors.NotFoundError(msg='用户不存在')
captcha_code = await redis_client.get(f'{settings.EMAIL_CAPTCHA_REDIS_PREFIX}:{ctx.ip}')
if not captcha_code:
raise errors.RequestError(msg='验证码已失效,请重新获取')
if captcha != captcha_code:
raise errors.CustomError(error=CustomErrorCode.CAPTCHA_ERROR)
await redis_client.delete(f'{settings.EMAIL_CAPTCHA_REDIS_PREFIX}:{ctx.ip}')
count = await user_dao.update_email(db, token_payload.id, email)
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
count = await user_dao.update_email(db, user_id, email)
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user_id}')
return count

@staticmethod
async def update_password(*, db: AsyncSession, request: Request, obj: ResetPasswordParam) -> int:
async def update_password(*, db: AsyncSession, user_id: int, hash_password: str, obj: ResetPasswordParam) -> int:
"""
更新当前用户密码

:param db: 数据库会话
:param request: FastAPI 请求对象
:param user_id: 用户 ID
:param hash_password: 哈希密码
:param obj: 密码重置参数
:return:
"""
token = get_token(request)
token_payload = jwt_decode(token)
user = await user_dao.get(db, token_payload.id)
if not user:
raise errors.NotFoundError(msg='用户不存在')
if not password_verify(obj.old_password, user.password):
if not password_verify(obj.old_password, hash_password):
raise errors.RequestError(msg='原密码错误')
if obj.new_password != obj.confirm_password:
raise errors.RequestError(msg='密码输入不一致')
count = await user_dao.reset_password(db, user.id, obj.new_password)
count = await user_dao.reset_password(db, user_id, obj.new_password)
key_prefix = [
f'{settings.TOKEN_REDIS_PREFIX}:{user.id}',
f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{user.id}',
f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}',
f'{settings.TOKEN_REDIS_PREFIX}:{user_id}',
f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{user_id}',
f'{settings.JWT_USER_REDIS_PREFIX}:{user_id}',
]
for prefix in key_prefix:
await redis_client.delete_prefix(prefix)
Expand Down
11 changes: 6 additions & 5 deletions backend/common/security/permission.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from sqlalchemy.ext.asyncio import AsyncSession

from backend.app.admin.crud.crud_data_scope import data_scope_dao
from backend.app.admin.schema.user import GetUserInfoWithRelationDetail
from backend.common.context import ctx
from backend.common.enums import RoleDataRuleExpressionType, RoleDataRuleOperatorType
from backend.common.exception import errors
Expand Down Expand Up @@ -42,28 +43,28 @@ async def __call__(self, request: Request) -> None:
ctx.permission = self.value


async def filter_data_permission(db: AsyncSession, request: Request) -> ColumnElement[bool]: # noqa: C901
async def filter_data_permission(db: AsyncSession, request_user: GetUserInfoWithRelationDetail) -> ColumnElement[bool]: # noqa: C901
"""
过滤数据权限,控制用户可见数据范围

使用场景:
- 控制用户能看到哪些数据

:param db: 数据库会话
:param request: FastAPI 请求对象
:param request_user: 请求用户
:return:
"""
# 是否过滤数据权限
if request.user.is_superuser:
if request_user.is_superuser:
return or_(1 == 1)

for role in request.user.roles:
for role in request_user.roles:
if not role.is_filter_scopes:
return or_(1 == 1)

# 获取数据范围
data_scope_ids = set()
for role in request.user.roles:
for role in request_user.roles:
for scope in role.scopes:
if scope.status:
data_scope_ids.add(scope.id)
Expand Down