Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
5 changes: 5 additions & 0 deletions backend/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from backend.common.i18n import i18n
from backend.utils.console import console

__version__ = '1.7.0'


def get_version() -> str | None:
console.print(f'[cyan]{__version__}[/]')


# 初始化 i18n
i18n.load_locales()
13 changes: 4 additions & 9 deletions backend/app/admin/api/v1/sys/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from backend.app.admin.service.plugin_service import plugin_service
from backend.common.enums import PluginType
from backend.common.i18n import t
from backend.common.response.response_code import CustomResponse
from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base
from backend.common.security.jwt import DependsJwtAuth
Expand Down Expand Up @@ -43,12 +44,8 @@ async def install_plugin(
file: Annotated[UploadFile | None, File()] = None,
repo_url: Annotated[str | None, Query(description='插件 git 仓库地址')] = None,
) -> ResponseModel:
plugin_name = await plugin_service.install(type=type, file=file, repo_url=repo_url)
return response_base.success(
res=CustomResponse(
code=200, msg=f'插件 {plugin_name} 安装成功,请根据插件说明(README.md)进行相关配置并重启服务'
)
)
plugin = await plugin_service.install(type=type, file=file, repo_url=repo_url)
return response_base.success(res=CustomResponse(code=200, msg=t('success.plugin.install_success', plugin=plugin)))


@router.delete(
Expand All @@ -62,9 +59,7 @@ async def install_plugin(
)
async def uninstall_plugin(plugin: Annotated[str, Path(description='插件名称')]) -> ResponseModel:
await plugin_service.uninstall(plugin=plugin)
return response_base.success(
res=CustomResponse(code=200, msg=f'插件 {plugin} 卸载成功,请根据插件说明(README.md)移除相关配置并重启服务')
)
return response_base.success(res=CustomResponse(code=200, msg=t('success.plugin.uninstall_success', plugin=plugin)))


@router.put(
Expand Down
23 changes: 12 additions & 11 deletions backend/app/admin/service/auth_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from backend.app.admin.service.login_log_service import login_log_service
from backend.common.enums import LoginLogStatusType
from backend.common.exception import errors
from backend.common.i18n import t
from backend.common.log import log
from backend.common.response.response_code import CustomErrorCode
from backend.common.security.jwt import (
Expand Down Expand Up @@ -44,16 +45,16 @@ async def user_verify(db: AsyncSession, username: str, password: str) -> User:
"""
user = await user_dao.get_by_username(db, username)
if not user:
raise errors.NotFoundError(msg='用户名或密码有误')
raise errors.NotFoundError(msg=t('error.username_or_password_error'))

if user.password is None:
raise errors.AuthorizationError(msg='用户名或密码有误')
raise errors.AuthorizationError(msg=t('error.username_or_password_error'))
else:
if not password_verify(password, user.password):
raise errors.AuthorizationError(msg='用户名或密码有误')
raise errors.AuthorizationError(msg=t('error.username_or_password_error'))

if not user.status:
raise errors.AuthorizationError(msg='用户已被锁定, 请联系统管理员')
raise errors.AuthorizationError(msg=t('error.user.locked'))

return user

Expand Down Expand Up @@ -93,7 +94,7 @@ async def login(
user = await self.user_verify(db, obj.username, obj.password)
captcha_code = await redis_client.get(f'{settings.CAPTCHA_LOGIN_REDIS_PREFIX}:{request.state.ip}')
if not captcha_code:
raise errors.RequestError(msg='验证码失效,请重新获取')
raise errors.RequestError(msg=t('error.captcha.expired'))
if captcha_code.lower() != obj.captcha.lower():
raise errors.CustomError(error=CustomErrorCode.CAPTCHA_ERROR)
await redis_client.delete(f'{settings.CAPTCHA_LOGIN_REDIS_PREFIX}:{request.state.ip}')
Expand Down Expand Up @@ -137,7 +138,7 @@ async def login(
msg=e.msg,
),
)
raise errors.RequestError(msg=e.msg, background=task)
raise errors.RequestError(code=e.code, msg=e.msg, background=task)
except Exception as e:
log.error(f'登陆错误: {e}')
raise e
Expand All @@ -151,7 +152,7 @@ async def login(
username=obj.username,
login_time=timezone.now(),
status=LoginLogStatusType.success.value,
msg='登录成功',
msg=t('success.login.success'),
),
)
data = GetLoginToken(
Expand Down Expand Up @@ -197,17 +198,17 @@ async def refresh_token(*, request: Request) -> GetNewToken:
"""
refresh_token = request.cookies.get(settings.COOKIE_REFRESH_TOKEN_KEY)
if not refresh_token:
raise errors.RequestError(msg='Refresh Token 已过期,请重新登录')
raise errors.RequestError(msg=t('error.refresh_token_expired'))
token_payload = jwt_decode(refresh_token)
async with async_db_session() as db:
user = await user_dao.get(db, token_payload.id)
if not user:
raise errors.NotFoundError(msg='用户不存在')
raise errors.NotFoundError(msg=t('error.user.not_found'))
elif not user.status:
raise errors.AuthorizationError(msg='用户已被锁定, 请联系统管理员')
raise errors.AuthorizationError(msg=t('error.user.locked'))
if not user.is_multi_login:
if await redis_client.keys(match=f'{settings.TOKEN_REDIS_PREFIX}:{user.id}:*'):
raise errors.ForbiddenError(msg='此用户已在异地登录,请重新登录并及时修改密码')
raise errors.ForbiddenError(msg=t('error.user.login_elsewhere'))
new_token = await create_new_token(
refresh_token,
token_payload.session_uuid,
Expand Down
11 changes: 6 additions & 5 deletions backend/app/admin/service/data_rule_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
UpdateDataRuleParam,
)
from backend.common.exception import errors
from backend.common.i18n import t
from backend.core.conf import settings
from backend.database.db import async_db_session
from backend.utils.import_parse import dynamic_import_data_model
Expand All @@ -32,7 +33,7 @@ async def get(*, pk: int) -> DataRule:
async with async_db_session() as db:
data_rule = await data_rule_dao.get(db, pk)
if not data_rule:
raise errors.NotFoundError(msg='数据规则不存在')
raise errors.NotFoundError(msg=t('error.data_rule.not_found'))
return data_rule

@staticmethod
Expand All @@ -49,7 +50,7 @@ async def get_columns(model: str) -> list[GetDataRuleColumnDetail]:
:return:
"""
if model not in settings.DATA_PERMISSION_MODELS:
raise errors.NotFoundError(msg='数据规则可用模型不存在')
raise errors.NotFoundError(msg=t('error.data_rule.available_models'))
model_ins = dynamic_import_data_model(settings.DATA_PERMISSION_MODELS[model])

model_columns = [
Expand Down Expand Up @@ -87,7 +88,7 @@ async def create(*, obj: CreateDataRuleParam) -> None:
async with async_db_session.begin() as db:
data_rule = await data_rule_dao.get_by_name(db, obj.name)
if data_rule:
raise errors.ConflictError(msg='数据规则已存在')
raise errors.ConflictError(msg=t('error.data_rule.exists'))
await data_rule_dao.create(db, obj)

@staticmethod
Expand All @@ -102,10 +103,10 @@ async def update(*, pk: int, obj: UpdateDataRuleParam) -> int:
async with async_db_session.begin() as db:
data_rule = await data_rule_dao.get(db, pk)
if not data_rule:
raise errors.NotFoundError(msg='数据规则不存在')
raise errors.NotFoundError(msg=t('error.data_rule.not_found'))
if data_rule.name != obj.name:
if await data_rule_dao.get_by_name(db, obj.name):
raise errors.ConflictError(msg='数据规则已存在')
raise errors.ConflictError(msg=t('error.data_rule.exists'))
count = await data_rule_dao.update(db, pk, obj)
return count

Expand Down
11 changes: 6 additions & 5 deletions backend/app/admin/service/data_scope_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
UpdateDataScopeRuleParam,
)
from backend.common.exception import errors
from backend.common.i18n import t
from backend.core.conf import settings
from backend.database.db import async_db_session
from backend.database.redis import redis_client
Expand All @@ -32,7 +33,7 @@ async def get(*, pk: int) -> DataScope:
async with async_db_session() as db:
data_scope = await data_scope_dao.get(db, pk)
if not data_scope:
raise errors.NotFoundError(msg='数据范围不存在')
raise errors.NotFoundError(msg=t('error.data_scope.not_found'))
return data_scope

@staticmethod
Expand All @@ -53,7 +54,7 @@ async def get_rules(*, pk: int) -> DataScope:
async with async_db_session() as db:
data_scope = await data_scope_dao.get_with_relation(db, pk)
if not data_scope:
raise errors.NotFoundError(msg='数据范围不存在')
raise errors.NotFoundError(msg=t('error.data_scope.not_found'))
return data_scope

@staticmethod
Expand All @@ -78,7 +79,7 @@ async def create(*, obj: CreateDataScopeParam) -> None:
async with async_db_session.begin() as db:
data_scope = await data_scope_dao.get_by_name(db, obj.name)
if data_scope:
raise errors.ConflictError(msg='数据范围已存在')
raise errors.ConflictError(msg=t('error.data_scope.exists'))
await data_scope_dao.create(db, obj)

@staticmethod
Expand All @@ -93,10 +94,10 @@ async def update(*, pk: int, obj: UpdateDataScopeParam) -> int:
async with async_db_session.begin() as db:
data_scope = await data_scope_dao.get(db, pk)
if not data_scope:
raise errors.NotFoundError(msg='数据范围不存在')
raise errors.NotFoundError(msg=t('error.data_scope.not_found'))
if data_scope.name != obj.name:
if await data_scope_dao.get_by_name(db, obj.name):
raise errors.ConflictError(msg='数据范围已存在')
raise errors.ConflictError(msg=t('error.data_scope.exists'))
count = await data_scope_dao.update(db, pk, obj)
for role in await data_scope.awaitable_attrs.roles:
for user in await role.awaitable_attrs.users:
Expand Down
19 changes: 10 additions & 9 deletions backend/app/admin/service/dept_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from backend.app.admin.model import Dept
from backend.app.admin.schema.dept import CreateDeptParam, UpdateDeptParam
from backend.common.exception import errors
from backend.common.i18n import t
from backend.core.conf import settings
from backend.database.db import async_db_session
from backend.database.redis import redis_client
Expand All @@ -28,7 +29,7 @@ async def get(*, pk: int) -> Dept:
async with async_db_session() as db:
dept = await dept_dao.get(db, pk)
if not dept:
raise errors.NotFoundError(msg='部门不存在')
raise errors.NotFoundError(msg=t('error.dept.not_found'))
return dept

@staticmethod
Expand Down Expand Up @@ -61,11 +62,11 @@ async def create(*, obj: CreateDeptParam) -> None:
async with async_db_session.begin() as db:
dept = await dept_dao.get_by_name(db, obj.name)
if dept:
raise errors.ConflictError(msg='部门名称已存在')
raise errors.ConflictError(msg=t('error.dept.exists'))
if obj.parent_id:
parent_dept = await dept_dao.get(db, obj.parent_id)
if not parent_dept:
raise errors.NotFoundError(msg='父级部门不存在')
raise errors.NotFoundError(msg=t('error.dept.not_found'))
await dept_dao.create(db, obj)

@staticmethod
Expand All @@ -80,16 +81,16 @@ async def update(*, pk: int, obj: UpdateDeptParam) -> int:
async with async_db_session.begin() as db:
dept = await dept_dao.get(db, pk)
if not dept:
raise errors.NotFoundError(msg='部门不存在')
raise errors.NotFoundError(msg=t('error.dept.not_found'))
if dept.name != obj.name:
if await dept_dao.get_by_name(db, obj.name):
raise errors.ConflictError(msg='部门名称已存在')
raise errors.ConflictError(msg=t('error.dept.exists'))
if obj.parent_id:
parent_dept = await dept_dao.get(db, obj.parent_id)
if not parent_dept:
raise errors.NotFoundError(msg='父级部门不存在')
raise errors.NotFoundError(msg=t('error.dept.parent.not_found'))
if obj.parent_id == dept.id:
raise errors.ForbiddenError(msg='禁止关联自身为父级')
raise errors.ForbiddenError(msg=t('error.dept.parent.related_self_not_allowed'))
count = await dept_dao.update(db, pk, obj)
return count

Expand All @@ -104,10 +105,10 @@ async def delete(*, pk: int) -> int:
async with async_db_session.begin() as db:
dept = await dept_dao.get_with_relation(db, pk)
if dept.users:
raise errors.ConflictError(msg='部门下存在用户,无法删除')
raise errors.ConflictError(msg=t('error.dept.exists_users'))
children = await dept_dao.get_children(db, pk)
if children:
raise errors.ConflictError(msg='部门下存在子部门,无法删除')
raise errors.ConflictError(msg=t('error.dept.exists_children'))
count = await dept_dao.delete(db, pk)
for user in dept.users:
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
Expand Down
17 changes: 9 additions & 8 deletions backend/app/admin/service/menu_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from backend.app.admin.model import Menu
from backend.app.admin.schema.menu import CreateMenuParam, UpdateMenuParam
from backend.common.exception import errors
from backend.common.i18n import t
from backend.core.conf import settings
from backend.database.db import async_db_session
from backend.database.redis import redis_client
Expand All @@ -28,7 +29,7 @@ async def get(*, pk: int) -> Menu:
async with async_db_session() as db:
menu = await menu_dao.get(db, menu_id=pk)
if not menu:
raise errors.NotFoundError(msg='菜单不存在')
raise errors.NotFoundError(msg=t('error.menu.not_found'))
return menu

@staticmethod
Expand Down Expand Up @@ -78,11 +79,11 @@ async def create(*, obj: CreateMenuParam) -> None:
async with async_db_session.begin() as db:
title = await menu_dao.get_by_title(db, obj.title)
if title:
raise errors.ConflictError(msg='菜单标题已存在')
raise errors.ConflictError(msg=t('error.menu.exists'))
if obj.parent_id:
parent_menu = await menu_dao.get(db, obj.parent_id)
if not parent_menu:
raise errors.NotFoundError(msg='父级菜单不存在')
raise errors.NotFoundError(msg=t('error.menu.not_found'))
await menu_dao.create(db, obj)

@staticmethod
Expand All @@ -97,16 +98,16 @@ async def update(*, pk: int, obj: UpdateMenuParam) -> int:
async with async_db_session.begin() as db:
menu = await menu_dao.get(db, pk)
if not menu:
raise errors.NotFoundError(msg='菜单不存在')
raise errors.NotFoundError(msg=t('error.menu.not_found'))
if menu.title != obj.title:
if await menu_dao.get_by_title(db, obj.title):
raise errors.ConflictError(msg='菜单标题已存在')
raise errors.ConflictError(msg=t('error.menu.exists'))
if obj.parent_id:
parent_menu = await menu_dao.get(db, obj.parent_id)
if not parent_menu:
raise errors.NotFoundError(msg='父级菜单不存在')
raise errors.NotFoundError(msg=t('error.menu.parent.not_found'))
if obj.parent_id == menu.id:
raise errors.ForbiddenError(msg='禁止关联自身为父级')
raise errors.ForbiddenError(msg=t('error.menu.parent.related_self_not_allowed'))
count = await menu_dao.update(db, pk, obj)
for role in await menu.awaitable_attrs.roles:
for user in await role.awaitable_attrs.users:
Expand All @@ -124,7 +125,7 @@ async def delete(*, pk: int) -> int:
async with async_db_session.begin() as db:
children = await menu_dao.get_children(db, pk)
if children:
raise errors.ConflictError(msg='菜单下存在子菜单,无法删除')
raise errors.ConflictError(msg=t('error.menu.exists_children'))
menu = await menu_dao.get(db, pk)
count = await menu_dao.delete(db, pk)
if menu:
Expand Down
11 changes: 6 additions & 5 deletions backend/app/admin/service/plugin_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from backend.common.enums import PluginType, StatusType
from backend.common.exception import errors
from backend.common.i18n import t
from backend.core.conf import settings
from backend.core.path_conf import PLUGIN_DIR
from backend.database.redis import redis_client
Expand Down Expand Up @@ -54,10 +55,10 @@ async def install(*, type: PluginType, file: UploadFile | None = None, repo_url:
"""
if type == PluginType.zip:
if not file:
raise errors.RequestError(msg='ZIP 压缩包不能为空')
raise errors.RequestError(msg=t('error.plugin.zip_invalid'))
return await install_zip_plugin(file)
if not repo_url:
raise errors.RequestError(msg='Git 仓库地址不能为空')
raise errors.RequestError(msg=t('error.plugin.git_url_invalid'))
return await install_git_plugin(repo_url)

@staticmethod
Expand All @@ -70,7 +71,7 @@ async def uninstall(*, plugin: str):
"""
plugin_dir = os.path.join(PLUGIN_DIR, plugin)
if not os.path.exists(plugin_dir):
raise errors.NotFoundError(msg='插件不存在')
raise errors.NotFoundError(msg=t('error.plugin.not_found'))
await uninstall_requirements_async(plugin)
bacup_dir = os.path.join(PLUGIN_DIR, f'{plugin}.{timezone.now().strftime("%Y%m%d%H%M%S")}.backup')
shutil.move(plugin_dir, bacup_dir)
Expand All @@ -87,7 +88,7 @@ async def update_status(*, plugin: str):
"""
plugin_info = await redis_client.get(f'{settings.PLUGIN_REDIS_PREFIX}:{plugin}')
if not plugin_info:
raise errors.NotFoundError(msg='插件不存在')
raise errors.NotFoundError(msg=t('error.plugin.not_found'))
plugin_info = json.loads(plugin_info)

# 更新持久缓存状态
Expand All @@ -109,7 +110,7 @@ async def build(*, plugin: str) -> io.BytesIO:
"""
plugin_dir = os.path.join(PLUGIN_DIR, plugin)
if not os.path.exists(plugin_dir):
raise errors.NotFoundError(msg='插件不存在')
raise errors.NotFoundError(msg=t('error.plugin.not_found'))

bio = io.BytesIO()
with zipfile.ZipFile(bio, 'w') as zf:
Expand Down
Loading