初始提交

This commit is contained in:
焦龙言
2026-07-24 09:43:10 +08:00
commit b5b4721456
793 changed files with 116305 additions and 0 deletions
@@ -0,0 +1,129 @@
from typing import Annotated
from fastapi import Path, Request, Response
from common.annotation.rate_limit_annotation import ApiRateLimit, ApiRateLimitPreset
from common.aspect.interface_auth import UserInterfaceAuthDependency
from common.aspect.pre_auth import PreAuthDependency
from common.constant import ApiNamespace
from common.router import APIRouterPro
from common.vo import DataResponseModel, ResponseBaseModel
from module_admin.entity.vo.cache_vo import CacheInfoModel, CacheMonitorModel
from module_admin.service.cache_service import CacheService
from utils.log_util import logger
from utils.response_util import ResponseUtil
cache_controller = APIRouterPro(
prefix='/monitor/cache', order_num=15, tags=['系统监控-缓存监控'], dependencies=[PreAuthDependency()]
)
@cache_controller.get(
'',
summary='获取缓存监控信息接口',
description='用于获取缓存监控信息',
response_model=DataResponseModel[CacheMonitorModel],
dependencies=[UserInterfaceAuthDependency('monitor:cache:list')],
)
async def get_monitor_cache_info(request: Request) -> Response:
# 获取全量数据
cache_info_query_result = await CacheService.get_cache_monitor_statistical_info_services(request)
logger.info('获取成功')
return ResponseUtil.success(data=cache_info_query_result)
@cache_controller.get(
'/getNames',
summary='获取缓存名称列表接口',
description='用于获取缓存名称列表',
response_model=DataResponseModel[list[CacheInfoModel]],
dependencies=[UserInterfaceAuthDependency('monitor:cache:list')],
)
async def get_monitor_cache_name(request: Request) -> Response:
# 获取全量数据
cache_name_list_result = await CacheService.get_cache_monitor_cache_name_services()
logger.info('获取成功')
return ResponseUtil.success(data=cache_name_list_result)
@cache_controller.get(
'/getKeys/{cache_name}',
summary='获取缓存键列表接口',
description='用于获取指定缓存名称下的所有缓存键列表',
response_model=DataResponseModel[list[str]],
dependencies=[UserInterfaceAuthDependency('monitor:cache:list')],
)
async def get_monitor_cache_key(request: Request, cache_name: Annotated[str, Path(description='缓存名称')]) -> Response:
# 获取全量数据
cache_key_list_result = await CacheService.get_cache_monitor_cache_key_services(request, cache_name)
logger.info('获取成功')
return ResponseUtil.success(data=cache_key_list_result)
@cache_controller.get(
'/getValue/{cache_name}/{cache_key}',
summary='获取缓存值接口',
description='用于获取指定缓存名称下的指定缓存键对应的值',
response_model=DataResponseModel[CacheInfoModel],
dependencies=[UserInterfaceAuthDependency('monitor:cache:list')],
)
async def get_monitor_cache_value(
request: Request,
cache_name: Annotated[str, Path(description='缓存名称')],
cache_key: Annotated[str, Path(description='缓存键')],
) -> Response:
# 获取全量数据
cache_value_list_result = await CacheService.get_cache_monitor_cache_value_services(request, cache_name, cache_key)
logger.info('获取成功')
return ResponseUtil.success(data=cache_value_list_result)
@cache_controller.delete(
'/clearCacheName/{cache_name}',
summary='清除缓存名称接口',
description='用于清除指定缓存名称下的所有缓存键值对',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('monitor:cache:list')],
)
@ApiRateLimit(namespace=ApiNamespace.MONITOR_CACHE_CLEAR_NAME, preset=ApiRateLimitPreset.USER_DESTRUCTIVE_MUTATION)
async def clear_monitor_cache_name(
request: Request, cache_name: Annotated[str, Path(description='缓存名称')]
) -> Response:
clear_cache_name_result = await CacheService.clear_cache_monitor_cache_name_services(request, cache_name)
logger.info(clear_cache_name_result.message)
return ResponseUtil.success(msg=clear_cache_name_result.message)
@cache_controller.delete(
'/clearCacheKey/{cache_key}',
summary='清除缓存键接口',
description='用于清除指定缓存键对应的值',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('monitor:cache:list')],
)
@ApiRateLimit(namespace=ApiNamespace.MONITOR_CACHE_CLEAR_KEY, preset=ApiRateLimitPreset.USER_DESTRUCTIVE_MUTATION)
async def clear_monitor_cache_key(request: Request, cache_key: Annotated[str, Path(description='缓存键')]) -> Response:
clear_cache_key_result = await CacheService.clear_cache_monitor_cache_key_services(request, cache_key)
logger.info(clear_cache_key_result.message)
return ResponseUtil.success(msg=clear_cache_key_result.message)
@cache_controller.delete(
'/clearCacheAll',
summary='清除所有缓存接口',
description='用于清除所有缓存键值对',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('monitor:cache:list')],
)
@ApiRateLimit(namespace=ApiNamespace.MONITOR_CACHE_CLEAR_ALL, preset=ApiRateLimitPreset.USER_DESTRUCTIVE_MUTATION)
async def clear_monitor_cache_all(request: Request) -> Response:
clear_cache_all_result = await CacheService.clear_cache_monitor_all_services(request)
logger.info(clear_cache_all_result.message)
return ResponseUtil.success(msg=clear_cache_all_result.message)
@@ -0,0 +1,46 @@
import uuid
from datetime import timedelta
from fastapi import Request, Response
from common.annotation.rate_limit_annotation import ApiRateLimit, ApiRateLimitPreset
from common.constant import ApiNamespace
from common.enums import RedisInitKeyConfig
from common.router import APIRouterPro
from common.vo import DynamicResponseModel
from module_admin.entity.vo.login_vo import CaptchaCode
from module_admin.service.captcha_service import CaptchaService
from utils.log_util import logger
from utils.response_util import ResponseUtil
captcha_controller = APIRouterPro(order_num=2, tags=['验证码模块'])
@captcha_controller.get(
'/captchaImage',
summary='获取图片验证码接口',
description='用于获取图片验证码',
response_model=DynamicResponseModel[CaptchaCode],
)
@ApiRateLimit(namespace=ApiNamespace.CAPTCHA_IMAGE, preset=ApiRateLimitPreset.ANON_AUTH_CAPTCHA)
async def get_captcha_image(request: Request) -> Response:
captcha_enabled = (
await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.captchaEnabled') == 'true'
)
register_enabled = (
await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.registerUser') == 'true'
)
session_id = str(uuid.uuid4())
captcha_result = await CaptchaService.create_captcha_image_service()
image = captcha_result[0]
computed_result = captcha_result[1]
await request.app.state.redis.set(
f'{RedisInitKeyConfig.CAPTCHA_CODES.key}:{session_id}', computed_result, ex=timedelta(minutes=2)
)
logger.info(f'编号为{session_id}的会话获取图片验证码成功')
return ResponseUtil.success(
model_content=CaptchaCode(
captchaEnabled=captcha_enabled, registerEnabled=register_enabled, img=image, uuid=session_id
)
)
@@ -0,0 +1,77 @@
from typing import Annotated
from fastapi import BackgroundTasks, File, Query, Request, Response, UploadFile
from fastapi.responses import StreamingResponse
from common.annotation.rate_limit_annotation import ApiRateLimit, ApiRateLimitPreset
from common.aspect.pre_auth import PreAuthDependency
from common.constant import ApiNamespace
from common.router import APIRouterPro
from common.vo import DynamicResponseModel
from module_admin.entity.vo.common_vo import UploadResponseModel
from module_admin.service.common_service import CommonService
from utils.log_util import logger
from utils.response_util import ResponseUtil
common_controller = APIRouterPro(prefix='/common', order_num=16, tags=['通用模块'], dependencies=[PreAuthDependency()])
@common_controller.post(
'/upload',
summary='通用文件上传接口',
description='用于上传文件',
response_model=DynamicResponseModel[UploadResponseModel],
)
@ApiRateLimit(namespace=ApiNamespace.COMMON_UPLOAD, preset=ApiRateLimitPreset.COMMON_UPLOAD)
async def common_upload(request: Request, file: Annotated[UploadFile, File(...)]) -> Response:
upload_result = await CommonService.upload_service(request, file)
logger.info('上传成功')
return ResponseUtil.success(model_content=upload_result.result)
@common_controller.get(
'/download',
summary='通用文件下载接口',
description='用于下载下载目录中的文件',
response_class=StreamingResponse,
responses={
200: {
'description': '流式返回文件',
'content': {
'application/octet-stream': {},
},
}
},
)
async def common_download(
request: Request,
background_tasks: BackgroundTasks,
file_name: Annotated[str, Query(alias='fileName')],
delete: Annotated[bool, Query()],
) -> Response:
download_result = await CommonService.download_services(background_tasks, file_name, delete)
logger.info(download_result.message)
return ResponseUtil.streaming(data=download_result.result)
@common_controller.get(
'/download/resource',
summary='通用资源文件下载接口',
description='用于下载上传目录中的资源文件',
response_class=StreamingResponse,
responses={
200: {
'description': '流式返回文件',
'content': {
'application/octet-stream': {},
},
}
},
)
async def common_download_resource(request: Request, resource: Annotated[str, Query()]) -> Response:
download_resource_result = await CommonService.download_resource_services(resource)
logger.info(download_resource_result.message)
return ResponseUtil.streaming(data=download_resource_result.result)
@@ -0,0 +1,201 @@
from datetime import datetime
from typing import Annotated
from fastapi import Form, Path, Query, Request, Response
from fastapi.responses import StreamingResponse
from pydantic_validation_decorator import ValidateFields
from sqlalchemy.ext.asyncio import AsyncSession
from common.annotation.cache_annotation import ApiCache, ApiCacheEvict
from common.annotation.log_annotation import Log
from common.annotation.rate_limit_annotation import ApiRateLimit, ApiRateLimitPreset
from common.aspect.db_seesion import DBSessionDependency
from common.aspect.interface_auth import UserInterfaceAuthDependency
from common.aspect.pre_auth import CurrentUserDependency, PreAuthDependency
from common.constant import ApiGroup, ApiNamespace
from common.enums import BusinessType
from common.router import APIRouterPro
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
from module_admin.entity.vo.config_vo import ConfigModel, ConfigPageQueryModel, DeleteConfigModel
from module_admin.entity.vo.user_vo import CurrentUserModel
from module_admin.service.config_service import ConfigService
from utils.common_util import bytes2file_response
from utils.log_util import logger
from utils.response_util import ResponseUtil
config_controller = APIRouterPro(
prefix='/system/config', order_num=9, tags=['系统管理-参数管理'], dependencies=[PreAuthDependency()]
)
@config_controller.get(
'/list',
summary='获取参数分页列表接口',
description='用于获取参数分页列表',
response_model=PageResponseModel[ConfigModel],
dependencies=[UserInterfaceAuthDependency('system:config:list')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_CONFIG_LIST)
async def get_system_config_list(
request: Request,
config_page_query: Annotated[ConfigPageQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
# 获取分页数据
config_page_query_result = await ConfigService.get_config_list_services(query_db, config_page_query, is_page=True)
logger.info('获取成功')
return ResponseUtil.success(model_content=config_page_query_result)
@config_controller.post(
'',
summary='新增参数接口',
description='用于新增参数',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:config:add')],
)
@ValidateFields(validate_model='add_config')
@ApiCacheEvict(namespaces=ApiGroup.CONFIG_MUTATION)
@Log(title='参数管理', business_type=BusinessType.INSERT)
async def add_system_config(
request: Request,
add_config: ConfigModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
add_config.create_by = current_user.user.user_name
add_config.create_time = datetime.now()
add_config.update_by = current_user.user.user_name
add_config.update_time = datetime.now()
add_config_result = await ConfigService.add_config_services(request, query_db, add_config)
logger.info(add_config_result.message)
return ResponseUtil.success(msg=add_config_result.message)
@config_controller.put(
'',
summary='编辑参数接口',
description='用于编辑参数',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:config:edit')],
)
@ValidateFields(validate_model='edit_config')
@ApiCacheEvict(namespaces=ApiGroup.CONFIG_MUTATION)
@Log(title='参数管理', business_type=BusinessType.UPDATE)
async def edit_system_config(
request: Request,
edit_config: ConfigModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
edit_config.update_by = current_user.user.user_name
edit_config.update_time = datetime.now()
edit_config_result = await ConfigService.edit_config_services(request, query_db, edit_config)
logger.info(edit_config_result.message)
return ResponseUtil.success(msg=edit_config_result.message)
@config_controller.delete(
'/refreshCache',
summary='刷新参数缓存接口',
description='用于刷新参数缓存',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:config:remove')],
)
@ApiRateLimit(namespace=ApiNamespace.SYSTEM_CONFIG_REFRESH_CACHE, preset=ApiRateLimitPreset.USER_COMMON_MUTATION)
@Log(title='参数管理', business_type=BusinessType.UPDATE)
async def refresh_system_config(
request: Request,
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
refresh_config_result = await ConfigService.refresh_sys_config_services(request, query_db)
logger.info(refresh_config_result.message)
return ResponseUtil.success(msg=refresh_config_result.message)
@config_controller.delete(
'/{config_ids}',
summary='删除参数接口',
description='用于删除参数',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:config:remove')],
)
@ApiCacheEvict(namespaces=ApiGroup.CONFIG_MUTATION)
@Log(title='参数管理', business_type=BusinessType.DELETE)
async def delete_system_config(
request: Request,
config_ids: Annotated[str, Path(description='需要删除的参数主键')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
delete_config = DeleteConfigModel(configIds=config_ids)
delete_config_result = await ConfigService.delete_config_services(request, query_db, delete_config)
logger.info(delete_config_result.message)
return ResponseUtil.success(msg=delete_config_result.message)
@config_controller.get(
'/{config_id}',
summary='获取参数详情接口',
description='用于获取指定参数的详细信息',
response_model=DataResponseModel[ConfigModel],
dependencies=[UserInterfaceAuthDependency('system:config:query')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_CONFIG_DETAIL)
async def query_detail_system_config(
request: Request,
config_id: Annotated[int, Path(description='参数主键')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
config_detail_result = await ConfigService.config_detail_services(query_db, config_id)
logger.info(f'获取config_id为{config_id}的信息成功')
return ResponseUtil.success(data=config_detail_result)
@config_controller.get(
'/configKey/{config_key}',
summary='根据参数键查询参数值接口',
description='用于根据参数键从缓存中查询参数值',
response_model=ResponseBaseModel,
)
async def query_system_config(request: Request, config_key: str) -> Response:
# 获取全量数据
config_query_result = await ConfigService.query_config_list_from_cache_services(request.app.state.redis, config_key)
logger.info('获取成功')
return ResponseUtil.success(msg=config_query_result)
@config_controller.post(
'/export',
summary='导出参数列表接口',
description='用于导出当前符合查询条件的参数列表数据',
response_class=StreamingResponse,
responses={
200: {
'description': '流式返回参数列表excel文件',
'content': {
'application/octet-stream': {},
},
}
},
dependencies=[UserInterfaceAuthDependency('system:config:export')],
)
@ApiRateLimit(namespace=ApiNamespace.SYSTEM_CONFIG_EXPORT, preset=ApiRateLimitPreset.USER_RESOURCE_EXPORT)
@Log(title='参数管理', business_type=BusinessType.EXPORT)
async def export_system_config_list(
request: Request,
config_page_query: Annotated[ConfigPageQueryModel, Form()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
# 获取全量数据
config_query_result = await ConfigService.get_config_list_services(query_db, config_page_query, is_page=False)
config_export_result = await ConfigService.export_config_list_services(config_query_result)
logger.info('导出成功')
return ResponseUtil.streaming(data=bytes2file_response(config_export_result))
@@ -0,0 +1,175 @@
from datetime import datetime
from typing import Annotated
from fastapi import Path, Query, Request, Response
from pydantic_validation_decorator import ValidateFields
from sqlalchemy import ColumnElement
from sqlalchemy.ext.asyncio import AsyncSession
from common.annotation.cache_annotation import ApiCache, ApiCacheEvict
from common.annotation.log_annotation import Log
from common.aspect.data_scope import DataScopeDependency
from common.aspect.db_seesion import DBSessionDependency
from common.aspect.interface_auth import UserInterfaceAuthDependency
from common.aspect.pre_auth import CurrentUserDependency, PreAuthDependency
from common.constant import ApiGroup, ApiNamespace
from common.enums import BusinessType
from common.router import APIRouterPro
from common.vo import DataResponseModel, ResponseBaseModel
from module_admin.entity.do.dept_do import SysDept
from module_admin.entity.vo.dept_vo import DeleteDeptModel, DeptModel, DeptQueryModel
from module_admin.entity.vo.user_vo import CurrentUserModel
from module_admin.service.dept_service import DeptService
from utils.log_util import logger
from utils.response_util import ResponseUtil
dept_controller = APIRouterPro(
prefix='/system/dept', order_num=6, tags=['系统管理-部门管理'], dependencies=[PreAuthDependency()]
)
@dept_controller.get(
'/list/exclude/{dept_id}',
summary='获取编辑部门的下拉树接口',
description='用于获取部门下拉树,不包含指定部门及其子部门',
response_model=DataResponseModel[list[DeptModel]],
dependencies=[UserInterfaceAuthDependency('system:dept:list')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_DEPT_EDIT_TREE)
async def get_system_dept_tree_for_edit_option(
request: Request,
dept_id: Annotated[int, Path(description='部门id')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
dept_query = DeptModel(deptId=dept_id)
dept_query_result = await DeptService.get_dept_for_edit_option_services(query_db, dept_query, data_scope_sql)
logger.info('获取成功')
return ResponseUtil.success(data=dept_query_result)
@dept_controller.get(
'/list',
summary='获取部门列表接口',
description='用于获取部门列表',
response_model=DataResponseModel[list[DeptModel]],
dependencies=[UserInterfaceAuthDependency('system:dept:list')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_DEPT_LIST)
async def get_system_dept_list(
request: Request,
dept_query: Annotated[DeptQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
dept_query_result = await DeptService.get_dept_list_services(query_db, dept_query, data_scope_sql)
logger.info('获取成功')
return ResponseUtil.success(data=dept_query_result)
@dept_controller.post(
'',
summary='新增部门接口',
description='用于新增部门',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:dept:add')],
)
@ValidateFields(validate_model='add_dept')
@ApiCacheEvict(namespaces=ApiGroup.DATA_SCOPE_MUTATION)
@Log(title='部门管理', business_type=BusinessType.INSERT)
async def add_system_dept(
request: Request,
add_dept: DeptModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
add_dept.create_by = current_user.user.user_name
add_dept.create_time = datetime.now()
add_dept.update_by = current_user.user.user_name
add_dept.update_time = datetime.now()
add_dept_result = await DeptService.add_dept_services(query_db, add_dept)
logger.info(add_dept_result.message)
return ResponseUtil.success(msg=add_dept_result.message)
@dept_controller.put(
'',
summary='编辑部门接口',
description='用于编辑部门',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:dept:edit')],
)
@ValidateFields(validate_model='edit_dept')
@ApiCacheEvict(namespaces=ApiGroup.DATA_SCOPE_MUTATION)
@Log(title='部门管理', business_type=BusinessType.UPDATE)
async def edit_system_dept(
request: Request,
edit_dept: DeptModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
if not current_user.user.admin:
await DeptService.check_dept_data_scope_services(query_db, edit_dept.dept_id, data_scope_sql)
edit_dept.update_by = current_user.user.user_name
edit_dept.update_time = datetime.now()
edit_dept_result = await DeptService.edit_dept_services(query_db, edit_dept)
logger.info(edit_dept_result.message)
return ResponseUtil.success(msg=edit_dept_result.message)
@dept_controller.delete(
'/{dept_ids}',
summary='删除部门接口',
description='用于删除部门',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:dept:remove')],
)
@ApiCacheEvict(namespaces=ApiGroup.DATA_SCOPE_MUTATION)
@Log(title='部门管理', business_type=BusinessType.DELETE)
async def delete_system_dept(
request: Request,
dept_ids: Annotated[str, Path(description='需要删除的部门id')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
dept_id_list = dept_ids.split(',') if dept_ids else []
if dept_id_list:
for dept_id in dept_id_list:
if not current_user.user.admin:
await DeptService.check_dept_data_scope_services(query_db, int(dept_id), data_scope_sql)
delete_dept = DeleteDeptModel(deptIds=dept_ids)
delete_dept.update_by = current_user.user.user_name
delete_dept.update_time = datetime.now()
delete_dept_result = await DeptService.delete_dept_services(query_db, delete_dept)
logger.info(delete_dept_result.message)
return ResponseUtil.success(msg=delete_dept_result.message)
@dept_controller.get(
'/{dept_id}',
summary='获取部门详情接口',
description='用于获取指定部门的详情信息',
response_model=DataResponseModel[DeptModel],
dependencies=[UserInterfaceAuthDependency('system:dept:query')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_DEPT_DETAIL)
async def query_detail_system_dept(
request: Request,
dept_id: Annotated[int, Path(description='部门id')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
if not current_user.user.admin:
await DeptService.check_dept_data_scope_services(query_db, dept_id, data_scope_sql)
detail_dept_result = await DeptService.dept_detail_services(query_db, dept_id)
logger.info(f'获取dept_id为{dept_id}的信息成功')
return ResponseUtil.success(data=detail_dept_result)
@@ -0,0 +1,377 @@
from datetime import datetime
from typing import Annotated
from fastapi import Form, Path, Query, Request, Response
from fastapi.responses import StreamingResponse
from pydantic_validation_decorator import ValidateFields
from sqlalchemy.ext.asyncio import AsyncSession
from common.annotation.cache_annotation import ApiCache, ApiCacheEvict
from common.annotation.log_annotation import Log
from common.annotation.rate_limit_annotation import ApiRateLimit, ApiRateLimitPreset
from common.aspect.db_seesion import DBSessionDependency
from common.aspect.interface_auth import UserInterfaceAuthDependency
from common.aspect.pre_auth import CurrentUserDependency, PreAuthDependency
from common.constant import ApiGroup, ApiNamespace
from common.enums import BusinessType
from common.router import APIRouterPro
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
from module_admin.entity.vo.dict_vo import (
DeleteDictDataModel,
DeleteDictTypeModel,
DictDataModel,
DictDataPageQueryModel,
DictTypeModel,
DictTypePageQueryModel,
)
from module_admin.entity.vo.user_vo import CurrentUserModel
from module_admin.service.dict_service import DictDataService, DictTypeService
from utils.common_util import bytes2file_response
from utils.log_util import logger
from utils.response_util import ResponseUtil
dict_controller = APIRouterPro(
prefix='/system/dict', order_num=8, tags=['系统管理-字典管理'], dependencies=[PreAuthDependency()]
)
@dict_controller.get(
'/type/list',
summary='获取字典类型分页列表接口',
description='用于获取字典类型分页列表',
response_model=PageResponseModel[DictTypeModel],
dependencies=[UserInterfaceAuthDependency('system:dict:list')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_DICT_TYPE_LIST)
async def get_system_dict_type_list(
request: Request,
dict_type_page_query: Annotated[DictTypePageQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
# 获取分页数据
dict_type_page_query_result = await DictTypeService.get_dict_type_list_services(
query_db, dict_type_page_query, is_page=True
)
logger.info('获取成功')
return ResponseUtil.success(model_content=dict_type_page_query_result)
@dict_controller.post(
'/type',
summary='新增字典类型接口',
description='用于新增字典类型',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:dict:add')],
)
@ValidateFields(validate_model='add_dict_type')
@ApiCacheEvict(namespaces=ApiGroup.DICT_TYPE_MUTATION)
@Log(title='字典类型', business_type=BusinessType.INSERT)
async def add_system_dict_type(
request: Request,
add_dict_type: DictTypeModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
add_dict_type.create_by = current_user.user.user_name
add_dict_type.create_time = datetime.now()
add_dict_type.update_by = current_user.user.user_name
add_dict_type.update_time = datetime.now()
add_dict_type_result = await DictTypeService.add_dict_type_services(request, query_db, add_dict_type)
logger.info(add_dict_type_result.message)
return ResponseUtil.success(msg=add_dict_type_result.message)
@dict_controller.put(
'/type',
summary='编辑字典类型接口',
description='用于编辑字典类型',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:dict:edit')],
)
@ValidateFields(validate_model='edit_dict_type')
@ApiCacheEvict(namespaces=ApiGroup.DICT_TYPE_MUTATION)
@Log(title='字典类型', business_type=BusinessType.UPDATE)
async def edit_system_dict_type(
request: Request,
edit_dict_type: DictTypeModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
edit_dict_type.update_by = current_user.user.user_name
edit_dict_type.update_time = datetime.now()
edit_dict_type_result = await DictTypeService.edit_dict_type_services(request, query_db, edit_dict_type)
logger.info(edit_dict_type_result.message)
return ResponseUtil.success(msg=edit_dict_type_result.message)
@dict_controller.delete(
'/type/refreshCache',
summary='刷新字典缓存接口',
description='用于刷新字典缓存',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:dict:remove')],
)
@ApiRateLimit(namespace=ApiNamespace.SYSTEM_DICT_REFRESH_CACHE, preset=ApiRateLimitPreset.USER_COMMON_MUTATION)
@Log(title='字典类型', business_type=BusinessType.UPDATE)
async def refresh_system_dict(request: Request, query_db: Annotated[AsyncSession, DBSessionDependency()]) -> Response:
refresh_dict_result = await DictTypeService.refresh_sys_dict_services(request, query_db)
logger.info(refresh_dict_result.message)
return ResponseUtil.success(msg=refresh_dict_result.message)
@dict_controller.delete(
'/type/{dict_ids}',
summary='删除字典类型接口',
description='用于删除字典类型',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:dict:remove')],
)
@ApiCacheEvict(namespaces=ApiGroup.DICT_TYPE_MUTATION)
@Log(title='字典类型', business_type=BusinessType.DELETE)
async def delete_system_dict_type(
request: Request,
dict_ids: Annotated[str, Path(description='需要删除的字典主键')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
delete_dict_type = DeleteDictTypeModel(dictIds=dict_ids)
delete_dict_type_result = await DictTypeService.delete_dict_type_services(request, query_db, delete_dict_type)
logger.info(delete_dict_type_result.message)
return ResponseUtil.success(msg=delete_dict_type_result.message)
@dict_controller.get(
'/type/optionselect',
summary='获取字典类型下拉列表接口',
description='用于获取字典类型下拉列表',
response_model=DataResponseModel[list[DictTypeModel]],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_DICT_TYPE_OPTIONS)
async def query_system_dict_type_options(
request: Request, query_db: Annotated[AsyncSession, DBSessionDependency()]
) -> Response:
dict_type_query_result = await DictTypeService.get_dict_type_list_services(
query_db, DictTypePageQueryModel(), is_page=False
)
logger.info('获取成功')
return ResponseUtil.success(data=dict_type_query_result)
@dict_controller.get(
'/type/{dict_id}',
summary='获取字典类型详情接口',
description='用于获取指定字典类型的详细信息',
response_model=DataResponseModel[DictTypeModel],
dependencies=[UserInterfaceAuthDependency('system:dict:query')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_DICT_TYPE_DETAIL)
async def query_detail_system_dict_type(
request: Request,
dict_id: Annotated[int, Path(description='字典主键')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
dict_type_detail_result = await DictTypeService.dict_type_detail_services(query_db, dict_id)
logger.info(f'获取dict_id为{dict_id}的信息成功')
return ResponseUtil.success(data=dict_type_detail_result)
@dict_controller.post(
'/type/export',
summary='导出字典类型列表接口',
description='用于导出当前符合查询条件的字典类型列表数据',
response_class=StreamingResponse,
responses={
200: {
'description': '流式返回字典类型列表excel文件',
'content': {
'application/octet-stream': {},
},
}
},
dependencies=[UserInterfaceAuthDependency('system:dict:export')],
)
@ApiRateLimit(namespace=ApiNamespace.SYSTEM_DICT_TYPE_EXPORT, preset=ApiRateLimitPreset.USER_RESOURCE_EXPORT)
@Log(title='字典类型', business_type=BusinessType.EXPORT)
async def export_system_dict_type_list(
request: Request,
dict_type_page_query: Annotated[DictTypePageQueryModel, Form()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
# 获取全量数据
dict_type_query_result = await DictTypeService.get_dict_type_list_services(
query_db, dict_type_page_query, is_page=False
)
dict_type_export_result = await DictTypeService.export_dict_type_list_services(dict_type_query_result)
logger.info('导出成功')
return ResponseUtil.streaming(data=bytes2file_response(dict_type_export_result))
@dict_controller.get(
'/data/type/{dict_type}',
summary='获取指定字典类型的数据列表接口',
description='用于从缓存中获取指定字典类型的所有数据项',
response_model=DataResponseModel[list[DictDataModel]],
)
async def query_system_dict_type_data(
request: Request,
dict_type: Annotated[str, Path(description='字典类型')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
# 获取全量数据
dict_data_query_result = await DictDataService.query_dict_data_list_from_cache_services(
request.app.state.redis, dict_type
)
logger.info('获取成功')
return ResponseUtil.success(data=dict_data_query_result)
@dict_controller.get(
'/data/list',
summary='获取字典数据分页列表接口',
description='用于获取字典数据分页列表',
response_model=PageResponseModel[DictDataModel],
dependencies=[UserInterfaceAuthDependency('system:dict:list')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_DICT_DATA_LIST)
async def get_system_dict_data_list(
request: Request,
dict_data_page_query: Annotated[DictDataPageQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
# 获取分页数据
dict_data_page_query_result = await DictDataService.get_dict_data_list_services(
query_db, dict_data_page_query, is_page=True
)
logger.info('获取成功')
return ResponseUtil.success(model_content=dict_data_page_query_result)
@dict_controller.post(
'/data',
summary='新增字典数据接口',
description='用于新增字典数据',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:dict:add')],
)
@ValidateFields(validate_model='add_dict_data')
@ApiCacheEvict(namespaces=ApiGroup.DICT_DATA_MUTATION)
@Log(title='字典数据', business_type=BusinessType.INSERT)
async def add_system_dict_data(
request: Request,
add_dict_data: DictDataModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
add_dict_data.create_by = current_user.user.user_name
add_dict_data.create_time = datetime.now()
add_dict_data.update_by = current_user.user.user_name
add_dict_data.update_time = datetime.now()
add_dict_data_result = await DictDataService.add_dict_data_services(request, query_db, add_dict_data)
logger.info(add_dict_data_result.message)
return ResponseUtil.success(msg=add_dict_data_result.message)
@dict_controller.put(
'/data',
summary='编辑字典数据接口',
description='用于编辑字典数据',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:dict:edit')],
)
@ValidateFields(validate_model='edit_dict_data')
@ApiCacheEvict(namespaces=ApiGroup.DICT_DATA_MUTATION)
@Log(title='字典数据', business_type=BusinessType.UPDATE)
async def edit_system_dict_data(
request: Request,
edit_dict_data: DictDataModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
edit_dict_data.update_by = current_user.user.user_name
edit_dict_data.update_time = datetime.now()
edit_dict_data_result = await DictDataService.edit_dict_data_services(request, query_db, edit_dict_data)
logger.info(edit_dict_data_result.message)
return ResponseUtil.success(msg=edit_dict_data_result.message)
@dict_controller.delete(
'/data/{dict_codes}',
summary='删除字典数据接口',
description='用于删除字典数据',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:dict:remove')],
)
@ApiCacheEvict(namespaces=ApiGroup.DICT_DATA_MUTATION)
@Log(title='字典数据', business_type=BusinessType.DELETE)
async def delete_system_dict_data(
request: Request,
dict_codes: Annotated[str, Path(description='需要删除的字典编码')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
delete_dict_data = DeleteDictDataModel(dictCodes=dict_codes)
delete_dict_data_result = await DictDataService.delete_dict_data_services(request, query_db, delete_dict_data)
logger.info(delete_dict_data_result.message)
return ResponseUtil.success(msg=delete_dict_data_result.message)
@dict_controller.get(
'/data/{dict_code}',
summary='获取字典数据详情接口',
description='用于获取指定字典数据的详细信息',
response_model=DataResponseModel[DictDataModel],
dependencies=[UserInterfaceAuthDependency('system:dict:query')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_DICT_DATA_DETAIL)
async def query_detail_system_dict_data(
request: Request,
dict_code: Annotated[int, Path(description='字典编码')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
detail_dict_data_result = await DictDataService.dict_data_detail_services(query_db, dict_code)
logger.info(f'获取dict_code为{dict_code}的信息成功')
return ResponseUtil.success(data=detail_dict_data_result)
@dict_controller.post(
'/data/export',
summary='导出字典数据列表接口',
description='用于导出当前符合查询条件的字典数据列表数据',
response_class=StreamingResponse,
responses={
200: {
'description': '流式返回字典数据列表excel文件',
'content': {
'application/octet-stream': {},
},
}
},
dependencies=[UserInterfaceAuthDependency('system:dict:export')],
)
@ApiRateLimit(namespace=ApiNamespace.SYSTEM_DICT_DATA_EXPORT, preset=ApiRateLimitPreset.USER_RESOURCE_EXPORT)
@Log(title='字典数据', business_type=BusinessType.EXPORT)
async def export_system_dict_data_list(
request: Request,
dict_data_page_query: Annotated[DictDataPageQueryModel, Form()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
# 获取全量数据
dict_data_query_result = await DictDataService.get_dict_data_list_services(
query_db, dict_data_page_query, is_page=False
)
dict_data_export_result = await DictDataService.export_dict_data_list_services(dict_data_query_result)
logger.info('导出成功')
return ResponseUtil.streaming(data=bytes2file_response(dict_data_export_result))
@@ -0,0 +1,318 @@
from datetime import datetime
from typing import Annotated
from fastapi import Form, Path, Query, Request, Response
from fastapi.responses import StreamingResponse
from pydantic_validation_decorator import ValidateFields
from sqlalchemy.ext.asyncio import AsyncSession
from common.annotation.cache_annotation import ApiCache, ApiCacheEvict
from common.annotation.log_annotation import Log
from common.annotation.rate_limit_annotation import ApiRateLimit, ApiRateLimitPreset
from common.aspect.db_seesion import DBSessionDependency
from common.aspect.interface_auth import UserInterfaceAuthDependency
from common.aspect.pre_auth import CurrentUserDependency, PreAuthDependency
from common.constant import ApiGroup, ApiNamespace
from common.enums import BusinessType
from common.router import APIRouterPro
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
from module_admin.entity.vo.job_vo import (
DeleteJobLogModel,
DeleteJobModel,
EditJobModel,
JobLogModel,
JobLogPageQueryModel,
JobModel,
JobPageQueryModel,
)
from module_admin.entity.vo.user_vo import CurrentUserModel
from module_admin.service.job_log_service import JobLogService
from module_admin.service.job_service import JobService
from utils.common_util import bytes2file_response
from utils.log_util import logger
from utils.response_util import ResponseUtil
job_controller = APIRouterPro(
prefix='/monitor', order_num=13, tags=['系统监控-定时任务'], dependencies=[PreAuthDependency()]
)
@job_controller.get(
'/job/list',
summary='获取定时任务分页列表接口',
description='用于获取定时任务分页列表',
response_model=PageResponseModel[JobModel],
dependencies=[UserInterfaceAuthDependency('monitor:job:list')],
)
@ApiCache(namespace=ApiNamespace.MONITOR_JOB_LIST)
async def get_system_job_list(
request: Request,
job_page_query: Annotated[JobPageQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
# 获取分页数据
job_page_query_result = await JobService.get_job_list_services(query_db, job_page_query, is_page=True)
logger.info('获取成功')
return ResponseUtil.success(model_content=job_page_query_result)
@job_controller.post(
'/job',
summary='新增定时任务接口',
description='用于新增定时任务',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('monitor:job:add')],
)
@ValidateFields(validate_model='add_job')
@ApiCacheEvict(namespaces=ApiGroup.JOB_MUTATION)
@Log(title='定时任务', business_type=BusinessType.INSERT)
async def add_system_job(
request: Request,
add_job: JobModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
add_job.create_by = current_user.user.user_name
add_job.create_time = datetime.now()
add_job.update_by = current_user.user.user_name
add_job.update_time = datetime.now()
add_job_result = await JobService.add_job_services(query_db, add_job)
logger.info(add_job_result.message)
return ResponseUtil.success(msg=add_job_result.message)
@job_controller.put(
'/job',
summary='编辑定时任务接口',
description='用于编辑定时任务',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('monitor:job:edit')],
)
@ValidateFields(validate_model='edit_job')
@ApiCacheEvict(namespaces=ApiGroup.JOB_MUTATION)
@Log(title='定时任务', business_type=BusinessType.UPDATE)
async def edit_system_job(
request: Request,
edit_job: EditJobModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
edit_job.update_by = current_user.user.user_name
edit_job.update_time = datetime.now()
edit_job_result = await JobService.edit_job_services(query_db, edit_job)
logger.info(edit_job_result.message)
return ResponseUtil.success(msg=edit_job_result.message)
@job_controller.put(
'/job/changeStatus',
summary='修改定时任务状态接口',
description='用于修改定时任务状态',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('monitor:job:changeStatus')],
)
@ApiCacheEvict(namespaces=ApiGroup.JOB_MUTATION)
@Log(title='定时任务', business_type=BusinessType.UPDATE)
async def change_system_job_status(
request: Request,
change_job: EditJobModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
edit_job = EditJobModel(
jobId=change_job.job_id,
status=change_job.status,
updateBy=current_user.user.user_name,
updateTime=datetime.now(),
type='status',
)
edit_job_result = await JobService.edit_job_services(query_db, edit_job)
logger.info(edit_job_result.message)
return ResponseUtil.success(msg=edit_job_result.message)
@job_controller.put(
'/job/run',
summary='执行定时任务接口',
description='用于执行指定的定时任务',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('monitor:job:changeStatus')],
)
@ApiRateLimit(namespace=ApiNamespace.MONITOR_JOB_RUN, preset=ApiRateLimitPreset.USER_RESOURCE_EXECUTION)
@ApiCacheEvict(namespaces=ApiGroup.JOB_MUTATION)
@Log(title='定时任务', business_type=BusinessType.UPDATE)
async def execute_system_job(
request: Request,
execute_job: JobModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
execute_job_result = await JobService.execute_job_once_services(query_db, execute_job)
logger.info(execute_job_result.message)
return ResponseUtil.success(msg=execute_job_result.message)
@job_controller.delete(
'/job/{job_ids}',
summary='删除定时任务接口',
description='用于删除定时任务',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('monitor:job:remove')],
)
@ApiRateLimit(namespace=ApiNamespace.MONITOR_JOB_DELETE, preset=ApiRateLimitPreset.USER_DESTRUCTIVE_MUTATION)
@ApiCacheEvict(namespaces=ApiGroup.JOB_MUTATION)
@Log(title='定时任务', business_type=BusinessType.DELETE)
async def delete_system_job(
request: Request,
job_ids: Annotated[str, Path(description='需要删除的定时任务ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
delete_job = DeleteJobModel(jobIds=job_ids)
delete_job_result = await JobService.delete_job_services(query_db, delete_job)
logger.info(delete_job_result.message)
return ResponseUtil.success(msg=delete_job_result.message)
@job_controller.get(
'/job/{job_id}',
summary='获取定时任务详情接口',
description='用于获取指定定时任务的详情信息',
response_model=DataResponseModel[JobModel],
dependencies=[UserInterfaceAuthDependency('monitor:job:query')],
)
@ApiCache(namespace=ApiNamespace.MONITOR_JOB_DETAIL)
async def query_detail_system_job(
request: Request,
job_id: Annotated[int, Path(description='任务ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
job_detail_result = await JobService.job_detail_services(query_db, job_id)
logger.info(f'获取job_id为{job_id}的信息成功')
return ResponseUtil.success(data=job_detail_result)
@job_controller.post(
'/job/export',
summary='导出定时任务列表接口',
description='用于导出当前符合查询条件的定时任务列表数据',
response_class=StreamingResponse,
responses={
200: {
'description': '流式返回定时任务列表excel文件',
'content': {
'application/octet-stream': {},
},
}
},
dependencies=[UserInterfaceAuthDependency('monitor:job:export')],
)
@ApiRateLimit(namespace=ApiNamespace.MONITOR_JOB_EXPORT, preset=ApiRateLimitPreset.USER_RESOURCE_EXPORT)
@Log(title='定时任务', business_type=BusinessType.EXPORT)
async def export_system_job_list(
request: Request,
job_page_query: Annotated[JobPageQueryModel, Form()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
# 获取全量数据
job_query_result = await JobService.get_job_list_services(query_db, job_page_query, is_page=False)
job_export_result = await JobService.export_job_list_services(request, job_query_result)
logger.info('导出成功')
return ResponseUtil.streaming(data=bytes2file_response(job_export_result))
@job_controller.get(
'/jobLog/list',
summary='获取定时任务调度日志分页列表接口',
description='用于获取定时任务调度日志分页列表',
response_model=PageResponseModel[JobLogModel],
dependencies=[UserInterfaceAuthDependency('monitor:job:list')],
)
async def get_system_job_log_list(
request: Request,
job_log_page_query: Annotated[JobLogPageQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
# 获取分页数据
job_log_page_query_result = await JobLogService.get_job_log_list_services(
query_db, job_log_page_query, is_page=True
)
logger.info('获取成功')
return ResponseUtil.success(model_content=job_log_page_query_result)
@job_controller.delete(
'/jobLog/clean',
summary='清空定时任务调度日志接口',
description='用于清空所有定时任务调度日志',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('monitor:job:remove')],
)
@ApiRateLimit(namespace=ApiNamespace.MONITOR_JOB_LOG_CLEAN, preset=ApiRateLimitPreset.USER_DESTRUCTIVE_MUTATION)
@Log(title='定时任务调度日志', business_type=BusinessType.CLEAN)
async def clear_system_job_log(
request: Request,
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
clear_job_log_result = await JobLogService.clear_job_log_services(query_db)
logger.info(clear_job_log_result.message)
return ResponseUtil.success(msg=clear_job_log_result.message)
@job_controller.delete(
'/jobLog/{job_log_ids}',
summary='删除定时任务调度日志接口',
description='用于删除定时任务调度日志',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('monitor:job:remove')],
)
@ApiRateLimit(namespace=ApiNamespace.MONITOR_JOB_LOG_DELETE, preset=ApiRateLimitPreset.USER_DESTRUCTIVE_MUTATION)
@Log(title='定时任务调度日志', business_type=BusinessType.DELETE)
async def delete_system_job_log(
request: Request,
job_log_ids: Annotated[str, Path(description='需要删除的定时任务日志ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
delete_job_log = DeleteJobLogModel(jobLogIds=job_log_ids)
delete_job_log_result = await JobLogService.delete_job_log_services(query_db, delete_job_log)
logger.info(delete_job_log_result.message)
return ResponseUtil.success(msg=delete_job_log_result.message)
@job_controller.post(
'/jobLog/export',
summary='导出定时任务调度日志列表接口',
description='用于导出当前符合查询条件的定时任务调度日志列表数据',
response_class=StreamingResponse,
responses={
200: {
'description': '流式返回定时任务日志列表excel文件',
'content': {
'application/octet-stream': {},
},
}
},
dependencies=[UserInterfaceAuthDependency('monitor:job:export')],
)
@ApiRateLimit(namespace=ApiNamespace.MONITOR_JOB_LOG_EXPORT, preset=ApiRateLimitPreset.USER_RESOURCE_EXPORT)
@Log(title='定时任务调度日志', business_type=BusinessType.EXPORT)
async def export_system_job_log_list(
request: Request,
job_log_page_query: Annotated[JobLogPageQueryModel, Form()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
# 获取全量数据
job_log_query_result = await JobLogService.get_job_log_list_services(query_db, job_log_page_query, is_page=False)
job_log_export_result = await JobLogService.export_job_log_list_services(request, job_log_query_result)
logger.info('导出成功')
return ResponseUtil.streaming(data=bytes2file_response(job_log_export_result))
@@ -0,0 +1,241 @@
from typing import Annotated
from fastapi import Form, Path, Query, Request, Response
from fastapi.responses import StreamingResponse
from sqlalchemy.ext.asyncio import AsyncSession
from common.annotation.log_annotation import Log
from common.annotation.rate_limit_annotation import ApiRateLimit, ApiRateLimitPreset
from common.aspect.db_seesion import DBSessionDependency
from common.aspect.interface_auth import UserInterfaceAuthDependency
from common.aspect.pre_auth import PreAuthDependency
from common.constant import ApiNamespace
from common.enums import BusinessType
from common.router import APIRouterPro
from common.vo import PageResponseModel, ResponseBaseModel
from module_admin.entity.vo.log_vo import (
DeleteLoginLogModel,
DeleteOperLogModel,
LogininforModel,
LoginLogPageQueryModel,
OperLogModel,
OperLogPageQueryModel,
UnlockUser,
)
from module_admin.service.log_service import LoginLogService, OperationLogService
from utils.common_util import bytes2file_response
from utils.log_util import logger
from utils.response_util import ResponseUtil
log_controller = APIRouterPro(
prefix='/monitor', order_num=11, tags=['系统管理-日志管理'], dependencies=[PreAuthDependency()]
)
@log_controller.get(
'/operlog/list',
summary='获取操作日志分页列表接口',
description='用于获取操作日志分页列表',
response_model=PageResponseModel[OperLogModel],
dependencies=[UserInterfaceAuthDependency('monitor:operlog:list')],
)
async def get_system_operation_log_list(
request: Request,
operation_log_page_query: Annotated[OperLogPageQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
# 获取分页数据
operation_log_page_query_result = await OperationLogService.get_operation_log_list_services(
query_db, operation_log_page_query, is_page=True
)
logger.info('获取成功')
return ResponseUtil.success(model_content=operation_log_page_query_result)
@log_controller.delete(
'/operlog/clean',
summary='清空操作日志接口',
description='用于清空所有操作日志',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('monitor:operlog:remove')],
)
@ApiRateLimit(namespace=ApiNamespace.MONITOR_OPERLOG_CLEAN, preset=ApiRateLimitPreset.USER_DESTRUCTIVE_MUTATION)
@Log(title='操作日志', business_type=BusinessType.CLEAN)
async def clear_system_operation_log(
request: Request, query_db: Annotated[AsyncSession, DBSessionDependency()]
) -> Response:
clear_operation_log_result = await OperationLogService.clear_operation_log_services(query_db)
logger.info(clear_operation_log_result.message)
return ResponseUtil.success(msg=clear_operation_log_result.message)
@log_controller.delete(
'/operlog/{oper_ids}',
summary='删除操作日志接口',
description='用于删除操作日志',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('monitor:operlog:remove')],
)
@ApiRateLimit(namespace=ApiNamespace.MONITOR_OPERLOG_DELETE, preset=ApiRateLimitPreset.USER_DESTRUCTIVE_MUTATION)
@Log(title='操作日志', business_type=BusinessType.DELETE)
async def delete_system_operation_log(
request: Request,
oper_ids: Annotated[str, Path(description='需要删除的日志主键')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
delete_operation_log = DeleteOperLogModel(operIds=oper_ids)
delete_operation_log_result = await OperationLogService.delete_operation_log_services(
query_db, delete_operation_log
)
logger.info(delete_operation_log_result.message)
return ResponseUtil.success(msg=delete_operation_log_result.message)
@log_controller.post(
'/operlog/export',
summary='导出操作日志接口',
description='用于导出当前符合查询条件的操作日志数据',
response_class=StreamingResponse,
responses={
200: {
'description': '流式返回操作日志列表excel文件',
'content': {
'application/octet-stream': {},
},
}
},
dependencies=[UserInterfaceAuthDependency('monitor:operlog:export')],
)
@ApiRateLimit(namespace=ApiNamespace.MONITOR_OPERLOG_EXPORT, preset=ApiRateLimitPreset.USER_RESOURCE_EXPORT)
@Log(title='操作日志', business_type=BusinessType.EXPORT)
async def export_system_operation_log_list(
request: Request,
operation_log_page_query: Annotated[OperLogPageQueryModel, Form()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
# 获取全量数据
operation_log_query_result = await OperationLogService.get_operation_log_list_services(
query_db, operation_log_page_query, is_page=False
)
operation_log_export_result = await OperationLogService.export_operation_log_list_services(
request, operation_log_query_result
)
logger.info('导出成功')
return ResponseUtil.streaming(data=bytes2file_response(operation_log_export_result))
@log_controller.get(
'/logininfor/list',
summary='获取登录日志分页列表接口',
description='用于获取登录日志分页列表',
response_model=PageResponseModel[LogininforModel],
dependencies=[UserInterfaceAuthDependency('monitor:logininfor:list')],
)
async def get_system_login_log_list(
request: Request,
login_log_page_query: Annotated[LoginLogPageQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
# 获取分页数据
login_log_page_query_result = await LoginLogService.get_login_log_list_services(
query_db, login_log_page_query, is_page=True
)
logger.info('获取成功')
return ResponseUtil.success(model_content=login_log_page_query_result)
@log_controller.delete(
'/logininfor/clean',
summary='清空登录日志接口',
description='用于清空所有登录日志',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('monitor:logininfor:remove')],
)
@ApiRateLimit(namespace=ApiNamespace.MONITOR_LOGININFO_CLEAN, preset=ApiRateLimitPreset.USER_DESTRUCTIVE_MUTATION)
@Log(title='登录日志', business_type=BusinessType.CLEAN)
async def clear_system_login_log(
request: Request, query_db: Annotated[AsyncSession, DBSessionDependency()]
) -> Response:
clear_login_log_result = await LoginLogService.clear_login_log_services(query_db)
logger.info(clear_login_log_result.message)
return ResponseUtil.success(msg=clear_login_log_result.message)
@log_controller.delete(
'/logininfor/{info_ids}',
summary='删除登录日志接口',
description='用于删除登录日志',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('monitor:logininfor:remove')],
)
@ApiRateLimit(namespace=ApiNamespace.MONITOR_LOGININFO_DELETE, preset=ApiRateLimitPreset.USER_DESTRUCTIVE_MUTATION)
@Log(title='登录日志', business_type=BusinessType.DELETE)
async def delete_system_login_log(
request: Request,
info_ids: Annotated[str, Path(description='需要删除的访问ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
delete_login_log = DeleteLoginLogModel(infoIds=info_ids)
delete_login_log_result = await LoginLogService.delete_login_log_services(query_db, delete_login_log)
logger.info(delete_login_log_result.message)
return ResponseUtil.success(msg=delete_login_log_result.message)
@log_controller.get(
'/logininfor/unlock/{user_name}',
summary='解锁账户接口',
description='用于解锁指定用户账户',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('monitor:logininfor:unlock')],
)
@ApiRateLimit(namespace=ApiNamespace.MONITOR_LOGININFO_UNLOCK, preset=ApiRateLimitPreset.USER_SECURITY_MUTATION)
@Log(title='账户解锁', business_type=BusinessType.OTHER)
async def unlock_system_user(
request: Request,
user_name: Annotated[str, Path(description='用户名称')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
unlock_user = UnlockUser(userName=user_name)
unlock_user_result = await LoginLogService.unlock_user_services(request, unlock_user)
logger.info(unlock_user_result.message)
return ResponseUtil.success(msg=unlock_user_result.message)
@log_controller.post(
'/logininfor/export',
summary='导出登录日志接口',
description='用于导出当前符合查询条件的登录日志数据',
response_class=StreamingResponse,
responses={
200: {
'description': '流式返回登录日志列表excel文件',
'content': {
'application/octet-stream': {},
},
}
},
dependencies=[UserInterfaceAuthDependency('monitor:logininfor:export')],
)
@ApiRateLimit(namespace=ApiNamespace.MONITOR_LOGININFO_EXPORT, preset=ApiRateLimitPreset.USER_RESOURCE_EXPORT)
@Log(title='登录日志', business_type=BusinessType.EXPORT)
async def export_system_login_log_list(
request: Request,
login_log_page_query: Annotated[LoginLogPageQueryModel, Form()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
# 获取全量数据
login_log_query_result = await LoginLogService.get_login_log_list_services(
query_db, login_log_page_query, is_page=False
)
login_log_export_result = await LoginLogService.export_login_log_list_services(login_log_query_result)
logger.info('导出成功')
return ResponseUtil.streaming(data=bytes2file_response(login_log_export_result))
@@ -0,0 +1,192 @@
import uuid
from datetime import datetime, timedelta
from typing import Annotated
import jwt
from fastapi import Depends, Request, Response
from sqlalchemy.ext.asyncio import AsyncSession
from common.annotation.cache_annotation import ApiCache, ApiCacheEvict
from common.annotation.log_annotation import Log
from common.annotation.rate_limit_annotation import ApiRateLimit, ApiRateLimitPreset
from common.aspect.db_seesion import DBSessionDependency
from common.aspect.pre_auth import CurrentUserDependency
from common.constant import ApiGroup, ApiNamespace
from common.enums import BusinessType, RedisInitKeyConfig
from common.router import APIRouterPro
from common.vo import CrudResponseModel, DataResponseModel, DynamicResponseModel, ResponseBaseModel
from config.env import AppConfig, JwtConfig
from module_admin.entity.vo.login_vo import LoginToken, RouterModel, Token, UserLogin, UserRegister
from module_admin.entity.vo.user_vo import CurrentUserModel, EditUserModel
from module_admin.service.login_service import CustomOAuth2PasswordRequestForm, LoginService, oauth2_scheme
from module_admin.service.user_service import UserService
from utils.log_util import logger
from utils.response_util import ResponseUtil
login_controller = APIRouterPro(order_num=1, tags=['登录模块'])
@login_controller.post(
'/login',
summary='登录接口',
description='用于用户登录',
response_model=DynamicResponseModel[LoginToken] | Token,
)
@ApiRateLimit(namespace=ApiNamespace.LOGIN, preset=ApiRateLimitPreset.ANON_AUTH_LOGIN)
@ApiCacheEvict(namespaces=ApiGroup.LOGIN_SUCCESS_MUTATION)
@Log(title='用户登录', business_type=BusinessType.OTHER, log_type='login')
async def login(
request: Request,
form_data: Annotated[CustomOAuth2PasswordRequestForm, Depends()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
captcha_enabled = (
await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.captchaEnabled') == 'true'
)
user = UserLogin(
userName=form_data.username,
password=form_data.password,
code=form_data.code,
uuid=form_data.uuid,
loginInfo=form_data.login_info,
captchaEnabled=captcha_enabled,
)
result = await LoginService.authenticate_user(request, query_db, user)
access_token_expires = timedelta(minutes=JwtConfig.jwt_expire_minutes)
session_id = str(uuid.uuid4())
access_token = await LoginService.create_access_token(
data={
'user_id': str(result[0].user_id),
'user_name': result[0].user_name,
'dept_name': result[1].dept_name if result[1] else None,
'session_id': session_id,
'login_info': user.login_info,
},
expires_delta=access_token_expires,
)
if AppConfig.app_same_time_login:
await request.app.state.redis.set(
f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}',
access_token,
ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes),
)
else:
# 此方法可实现同一账号同一时间只能登录一次
await request.app.state.redis.set(
f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{result[0].user_id}',
access_token,
ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes),
)
await UserService.edit_user_services(
query_db, EditUserModel(userId=result[0].user_id, loginDate=datetime.now(), type='status')
)
logger.info('登录成功')
# 判断请求是否来自于api文档,如果是返回指定格式的结果,用于修复api文档认证成功后token显示undefined的bug
request_from_swagger = request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False
request_from_redoc = request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False
if request_from_swagger or request_from_redoc:
return {'access_token': access_token, 'token_type': 'Bearer'}
return ResponseUtil.success(msg='登录成功', dict_content={'token': access_token})
@login_controller.get(
'/getInfo',
summary='获取用户信息接口',
description='用于获取当前登录用户的信息',
response_model=DynamicResponseModel[CurrentUserModel],
)
@ApiCache(namespace=ApiNamespace.LOGIN_USER_INFO)
async def get_login_user_info(
request: Request, current_user: Annotated[CurrentUserModel, CurrentUserDependency()]
) -> Response:
logger.info('获取成功')
return ResponseUtil.success(model_content=current_user)
@login_controller.get(
'/getRouters',
summary='获取用户路由接口',
description='用于获取当前登录用户的路由信息',
response_model=DataResponseModel[list[RouterModel]],
)
@ApiCache(namespace=ApiNamespace.LOGIN_USER_ROUTERS)
async def get_login_user_routers(
request: Request,
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
logger.info('获取成功')
user_routers = await LoginService.get_current_user_routers(current_user.user.user_id, query_db)
return ResponseUtil.success(data=user_routers)
@login_controller.post(
'/register',
summary='注册接口',
description='用于用户注册',
response_model=DataResponseModel[CrudResponseModel],
)
@ApiRateLimit(namespace=ApiNamespace.REGISTER, preset=ApiRateLimitPreset.ANON_AUTH_REGISTER)
@ApiCacheEvict(namespaces=ApiGroup.USER_ENTITY_MUTATION)
async def register_user(
request: Request,
user_register: UserRegister,
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
user_register_result = await LoginService.register_user_services(request, query_db, user_register)
logger.info(user_register_result.message)
return ResponseUtil.success(data=user_register_result, msg=user_register_result.message)
# @login_controller.post("/getSmsCode", response_model=SmsCode)
# async def get_sms_code(request: Request, user: ResetUserModel, query_db: AsyncSession = DBSessionDependency()):
# try:
# sms_result = await LoginService.get_sms_code_services(request, query_db, user)
# if sms_result.is_success:
# logger.info('获取成功')
# return ResponseUtil.success(data=sms_result)
# else:
# logger.warning(sms_result.message)
# return ResponseUtil.failure(msg=sms_result.message)
# except Exception as e:
# logger.exception(e)
# return ResponseUtil.error(msg=str(e))
#
#
# @login_controller.post("/forgetPwd", response_model=CrudResponseModel)
# async def forget_user_pwd(request: Request, forget_user: ResetUserModel, query_db: AsyncSession = DBSessionDependency()):
# try:
# forget_user_result = await LoginService.forget_user_services(request, query_db, forget_user)
# if forget_user_result.is_success:
# logger.info(forget_user_result.message)
# return ResponseUtil.success(data=forget_user_result, msg=forget_user_result.message)
# else:
# logger.warning(forget_user_result.message)
# return ResponseUtil.failure(msg=forget_user_result.message)
# except Exception as e:
# logger.exception(e)
# return ResponseUtil.error(msg=str(e))
@login_controller.post(
'/logout',
summary='退出登录接口',
description='用于用户退出登录',
response_model=ResponseBaseModel,
)
@ApiCacheEvict(namespaces=ApiGroup.LOGOUT_MUTATION)
async def logout(request: Request, token: Annotated[str | None, Depends(oauth2_scheme)]) -> Response:
payload = jwt.decode(
token, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm], options={'verify_exp': False}
)
if AppConfig.app_same_time_login:
token_id: str = payload.get('session_id')
else:
token_id: str = payload.get('user_id')
await LoginService.logout_services(request, token_id)
logger.info('退出成功')
return ResponseUtil.success(msg='退出成功')
@@ -0,0 +1,173 @@
from datetime import datetime
from typing import Annotated
from fastapi import Path, Query, Request, Response
from pydantic_validation_decorator import ValidateFields
from sqlalchemy.ext.asyncio import AsyncSession
from common.annotation.cache_annotation import ApiCache, ApiCacheEvict
from common.annotation.log_annotation import Log
from common.aspect.db_seesion import DBSessionDependency
from common.aspect.interface_auth import UserInterfaceAuthDependency
from common.aspect.pre_auth import CurrentUserDependency, PreAuthDependency
from common.constant import ApiGroup, ApiNamespace
from common.enums import BusinessType
from common.router import APIRouterPro
from common.vo import DataResponseModel, DynamicResponseModel, ResponseBaseModel
from module_admin.entity.vo.menu_vo import DeleteMenuModel, MenuModel, MenuQueryModel, MenuTreeModel
from module_admin.entity.vo.role_vo import RoleMenuQueryModel
from module_admin.entity.vo.user_vo import CurrentUserModel
from module_admin.service.menu_service import MenuService
from utils.log_util import logger
from utils.response_util import ResponseUtil
menu_controller = APIRouterPro(
prefix='/system/menu', order_num=5, tags=['系统管理-菜单管理'], dependencies=[PreAuthDependency()]
)
@menu_controller.get(
'/treeselect',
summary='获取菜单树接口',
description='用于获取当前用户可见的菜单树',
response_model=DataResponseModel[list[MenuTreeModel]],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_MENU_TREE)
async def get_system_menu_tree(
request: Request,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
menu_query_result = await MenuService.get_menu_tree_services(query_db, current_user)
logger.info('获取成功')
return ResponseUtil.success(data=menu_query_result)
@menu_controller.get(
'/roleMenuTreeselect/{role_id}',
summary='获取角色菜单树接口',
description='用于获取指定角色可见的菜单树',
response_model=DynamicResponseModel[RoleMenuQueryModel],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_MENU_ROLE_TREE)
async def get_system_role_menu_tree(
request: Request,
role_id: Annotated[int, Path(description='角色ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
role_menu_query_result = await MenuService.get_role_menu_tree_services(query_db, role_id, current_user)
logger.info('获取成功')
return ResponseUtil.success(model_content=role_menu_query_result)
@menu_controller.get(
'/list',
summary='获取菜单列表接口',
description='用于获取当前用户可见的菜单列表',
response_model=DataResponseModel[list[MenuModel]],
dependencies=[UserInterfaceAuthDependency('system:menu:list')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_MENU_LIST)
async def get_system_menu_list(
request: Request,
menu_query: Annotated[MenuQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
menu_query_result = await MenuService.get_menu_list_services(query_db, menu_query, current_user)
logger.info('获取成功')
return ResponseUtil.success(data=menu_query_result)
@menu_controller.post(
'',
summary='新增菜单接口',
description='用于新增菜单',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:menu:add')],
)
@ValidateFields(validate_model='add_menu')
@ApiCacheEvict(namespaces=ApiGroup.MENU_MUTATION)
@Log(title='菜单管理', business_type=BusinessType.INSERT)
async def add_system_menu(
request: Request,
add_menu: MenuModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
add_menu.create_by = current_user.user.user_name
add_menu.create_time = datetime.now()
add_menu.update_by = current_user.user.user_name
add_menu.update_time = datetime.now()
add_menu_result = await MenuService.add_menu_services(query_db, add_menu)
logger.info(add_menu_result.message)
return ResponseUtil.success(msg=add_menu_result.message)
@menu_controller.put(
'',
summary='编辑菜单接口',
description='用于编辑菜单',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:menu:edit')],
)
@ValidateFields(validate_model='edit_menu')
@ApiCacheEvict(namespaces=ApiGroup.MENU_MUTATION)
@Log(title='菜单管理', business_type=BusinessType.UPDATE)
async def edit_system_menu(
request: Request,
edit_menu: MenuModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
edit_menu.update_by = current_user.user.user_name
edit_menu.update_time = datetime.now()
edit_menu_result = await MenuService.edit_menu_services(query_db, edit_menu)
logger.info(edit_menu_result.message)
return ResponseUtil.success(msg=edit_menu_result.message)
@menu_controller.delete(
'/{menu_ids}',
summary='删除菜单接口',
description='用于删除菜单',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:menu:remove')],
)
@ApiCacheEvict(namespaces=ApiGroup.MENU_MUTATION)
@Log(title='菜单管理', business_type=BusinessType.DELETE)
async def delete_system_menu(
request: Request,
menu_ids: Annotated[str, Path(description='需要删除的菜单ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
delete_menu = DeleteMenuModel(menuIds=menu_ids)
delete_menu_result = await MenuService.delete_menu_services(query_db, delete_menu)
logger.info(delete_menu_result.message)
return ResponseUtil.success(msg=delete_menu_result.message)
@menu_controller.get(
'/{menu_id}',
summary='获取菜单详情接口',
description='用于获取指定菜单的详情信息',
response_model=DataResponseModel[MenuModel],
dependencies=[UserInterfaceAuthDependency('system:menu:query')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_MENU_DETAIL)
async def query_detail_system_menu(
request: Request,
menu_id: Annotated[int, Path(description='菜单ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
menu_detail_result = await MenuService.menu_detail_services(query_db, menu_id)
logger.info(f'获取menu_id为{menu_id}的信息成功')
return ResponseUtil.success(data=menu_detail_result)
@@ -0,0 +1,135 @@
from datetime import datetime
from typing import Annotated
from fastapi import Path, Query, Request, Response
from pydantic_validation_decorator import ValidateFields
from sqlalchemy.ext.asyncio import AsyncSession
from common.annotation.cache_annotation import ApiCache, ApiCacheEvict
from common.annotation.log_annotation import Log
from common.aspect.db_seesion import DBSessionDependency
from common.aspect.interface_auth import UserInterfaceAuthDependency
from common.aspect.pre_auth import CurrentUserDependency, PreAuthDependency
from common.constant import ApiGroup, ApiNamespace
from common.enums import BusinessType
from common.router import APIRouterPro
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
from module_admin.entity.vo.notice_vo import DeleteNoticeModel, NoticeModel, NoticePageQueryModel
from module_admin.entity.vo.user_vo import CurrentUserModel
from module_admin.service.notice_service import NoticeService
from utils.log_util import logger
from utils.response_util import ResponseUtil
notice_controller = APIRouterPro(
prefix='/system/notice', order_num=10, tags=['系统管理-通知公告管理'], dependencies=[PreAuthDependency()]
)
@notice_controller.get(
'/list',
summary='获取通知公告分页列表接口',
description='用于获取通知公告分页列表',
response_model=PageResponseModel[NoticeModel],
dependencies=[UserInterfaceAuthDependency('system:notice:list')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_NOTICE_LIST)
async def get_system_notice_list(
request: Request,
notice_page_query: Annotated[NoticePageQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
# 获取分页数据
notice_page_query_result = await NoticeService.get_notice_list_services(query_db, notice_page_query, is_page=True)
logger.info('获取成功')
return ResponseUtil.success(model_content=notice_page_query_result)
@notice_controller.post(
'',
summary='新增通知公告接口',
description='用于新增通知公告',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:notice:add')],
)
@ValidateFields(validate_model='add_notice')
@ApiCacheEvict(namespaces=ApiGroup.NOTICE_MUTATION)
@Log(title='通知公告', business_type=BusinessType.INSERT)
async def add_system_notice(
request: Request,
add_notice: NoticeModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
add_notice.create_by = current_user.user.user_name
add_notice.create_time = datetime.now()
add_notice.update_by = current_user.user.user_name
add_notice.update_time = datetime.now()
add_notice_result = await NoticeService.add_notice_services(query_db, add_notice)
logger.info(add_notice_result.message)
return ResponseUtil.success(msg=add_notice_result.message)
@notice_controller.put(
'',
summary='编辑通知公告接口',
description='用于编辑通知公告',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:notice:edit')],
)
@ValidateFields(validate_model='edit_notice')
@ApiCacheEvict(namespaces=ApiGroup.NOTICE_MUTATION)
@Log(title='通知公告', business_type=BusinessType.UPDATE)
async def edit_system_notice(
request: Request,
edit_notice: NoticeModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
edit_notice.update_by = current_user.user.user_name
edit_notice.update_time = datetime.now()
edit_notice_result = await NoticeService.edit_notice_services(query_db, edit_notice)
logger.info(edit_notice_result.message)
return ResponseUtil.success(msg=edit_notice_result.message)
@notice_controller.delete(
'/{notice_ids}',
summary='删除通知公告接口',
description='用于删除通知公告',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:notice:remove')],
)
@ApiCacheEvict(namespaces=ApiGroup.NOTICE_MUTATION)
@Log(title='通知公告', business_type=BusinessType.DELETE)
async def delete_system_notice(
request: Request,
notice_ids: Annotated[str, Path(description='需要删除的公告ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
delete_notice = DeleteNoticeModel(noticeIds=notice_ids)
delete_notice_result = await NoticeService.delete_notice_services(query_db, delete_notice)
logger.info(delete_notice_result.message)
return ResponseUtil.success(msg=delete_notice_result.message)
@notice_controller.get(
'/{notice_id}',
summary='获取通知公告详情接口',
description='用于获取指定通知公告的详细信息',
response_model=DataResponseModel[NoticeModel],
dependencies=[UserInterfaceAuthDependency('system:notice:query')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_NOTICE_DETAIL)
async def query_detail_system_post(
request: Request,
notice_id: Annotated[int, Path(description='公告ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
notice_detail_result = await NoticeService.notice_detail_services(query_db, notice_id)
logger.info(f'获取notice_id为{notice_id}的信息成功')
return ResponseUtil.success(data=notice_detail_result)
@@ -0,0 +1,63 @@
from typing import Annotated
from fastapi import Path, Query, Request, Response
from sqlalchemy.ext.asyncio import AsyncSession
from common.annotation.log_annotation import Log
from common.annotation.rate_limit_annotation import ApiRateLimit, ApiRateLimitPreset
from common.aspect.db_seesion import DBSessionDependency
from common.aspect.interface_auth import UserInterfaceAuthDependency
from common.aspect.pre_auth import PreAuthDependency
from common.constant import ApiNamespace
from common.enums import BusinessType
from common.router import APIRouterPro
from common.vo import ResponseBaseModel
from module_admin.entity.vo.online_vo import DeleteOnlineModel, OnlinePageResponseModel, OnlineQueryModel
from module_admin.service.online_service import OnlineService
from utils.log_util import logger
from utils.response_util import ResponseUtil
online_controller = APIRouterPro(
prefix='/monitor/online', order_num=12, tags=['系统监控-在线用户'], dependencies=[PreAuthDependency()]
)
@online_controller.get(
'/list',
summary='获取在线用户分页列表接口',
description='用于获取在线用户分页列表',
response_model=OnlinePageResponseModel,
dependencies=[UserInterfaceAuthDependency('monitor:online:list')],
)
async def get_monitor_online_list(
request: Request,
online_page_query: Annotated[OnlineQueryModel, Query()],
) -> Response:
# 获取全量数据
online_query_result = await OnlineService.get_online_list_services(request, online_page_query)
logger.info('获取成功')
return ResponseUtil.success(
model_content=OnlinePageResponseModel(rows=online_query_result, total=len(online_query_result))
)
@online_controller.delete(
'/{token_ids}',
summary='强退在线用户接口',
description='用于强退指定会话编号的在线用户',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('monitor:online:forceLogout')],
)
@ApiRateLimit(namespace=ApiNamespace.MONITOR_ONLINE_FORCE_LOGOUT, preset=ApiRateLimitPreset.USER_SECURITY_MUTATION)
@Log(title='在线用户', business_type=BusinessType.FORCE)
async def delete_monitor_online(
request: Request,
token_ids: Annotated[str, Path(description='需要强退的会话编号')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
delete_online = DeleteOnlineModel(tokenIds=token_ids)
delete_online_result = await OnlineService.delete_online_services(request, delete_online)
logger.info(delete_online_result.message)
return ResponseUtil.success(msg=delete_online_result.message)
@@ -0,0 +1,168 @@
from datetime import datetime
from typing import Annotated
from fastapi import Form, Path, Query, Request, Response
from fastapi.responses import StreamingResponse
from pydantic_validation_decorator import ValidateFields
from sqlalchemy.ext.asyncio import AsyncSession
from common.annotation.cache_annotation import ApiCache, ApiCacheEvict
from common.annotation.log_annotation import Log
from common.annotation.rate_limit_annotation import ApiRateLimit, ApiRateLimitPreset
from common.aspect.db_seesion import DBSessionDependency
from common.aspect.interface_auth import UserInterfaceAuthDependency
from common.aspect.pre_auth import CurrentUserDependency, PreAuthDependency
from common.constant import ApiGroup, ApiNamespace
from common.enums import BusinessType
from common.router import APIRouterPro
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
from module_admin.entity.vo.post_vo import DeletePostModel, PostModel, PostPageQueryModel
from module_admin.entity.vo.user_vo import CurrentUserModel
from module_admin.service.post_service import PostService
from utils.common_util import bytes2file_response
from utils.log_util import logger
from utils.response_util import ResponseUtil
post_controller = APIRouterPro(
prefix='/system/post', order_num=7, tags=['系统管理-岗位管理'], dependencies=[PreAuthDependency()]
)
@post_controller.get(
'/list',
summary='获取岗位分页列表接口',
description='用于获取岗位分页列表',
response_model=PageResponseModel[PostModel],
dependencies=[UserInterfaceAuthDependency('system:post:list')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_POST_LIST)
async def get_system_post_list(
request: Request,
post_page_query: Annotated[PostPageQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
# 获取分页数据
post_page_query_result = await PostService.get_post_list_services(query_db, post_page_query, is_page=True)
logger.info('获取成功')
return ResponseUtil.success(model_content=post_page_query_result)
@post_controller.post(
'',
summary='新增岗位接口',
description='用于新增岗位',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:post:add')],
)
@ValidateFields(validate_model='add_post')
@ApiCacheEvict(namespaces=ApiGroup.POST_MUTATION)
@Log(title='岗位管理', business_type=BusinessType.INSERT)
async def add_system_post(
request: Request,
add_post: PostModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
add_post.create_by = current_user.user.user_name
add_post.create_time = datetime.now()
add_post.update_by = current_user.user.user_name
add_post.update_time = datetime.now()
add_post_result = await PostService.add_post_services(query_db, add_post)
logger.info(add_post_result.message)
return ResponseUtil.success(msg=add_post_result.message)
@post_controller.put(
'',
summary='编辑岗位接口',
description='用于编辑岗位',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:post:edit')],
)
@ValidateFields(validate_model='edit_post')
@ApiCacheEvict(namespaces=ApiGroup.POST_MUTATION)
@Log(title='岗位管理', business_type=BusinessType.UPDATE)
async def edit_system_post(
request: Request,
edit_post: PostModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
edit_post.update_by = current_user.user.user_name
edit_post.update_time = datetime.now()
edit_post_result = await PostService.edit_post_services(query_db, edit_post)
logger.info(edit_post_result.message)
return ResponseUtil.success(msg=edit_post_result.message)
@post_controller.delete(
'/{post_ids}',
summary='删除岗位接口',
description='用于删除岗位',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:post:remove')],
)
@ApiCacheEvict(namespaces=ApiGroup.POST_MUTATION)
@Log(title='岗位管理', business_type=BusinessType.DELETE)
async def delete_system_post(
request: Request,
post_ids: Annotated[str, Path(description='需要删除的岗位ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
delete_post = DeletePostModel(postIds=post_ids)
delete_post_result = await PostService.delete_post_services(query_db, delete_post)
logger.info(delete_post_result.message)
return ResponseUtil.success(msg=delete_post_result.message)
@post_controller.get(
'/{post_id}',
summary='获取岗位详情接口',
description='用于获取指定岗位的详细信息',
response_model=DataResponseModel[PostModel],
dependencies=[UserInterfaceAuthDependency('system:post:query')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_POST_DETAIL)
async def query_detail_system_post(
request: Request,
post_id: Annotated[int, Path(description='岗位ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
post_detail_result = await PostService.post_detail_services(query_db, post_id)
logger.info(f'获取post_id为{post_id}的信息成功')
return ResponseUtil.success(data=post_detail_result)
@post_controller.post(
'/export',
summary='导出岗位列表接口',
description='用于导出当前符合查询条件的岗位列表数据',
response_class=StreamingResponse,
responses={
200: {
'description': '流式返回岗位列表excel文件',
'content': {
'application/octet-stream': {},
},
}
},
dependencies=[UserInterfaceAuthDependency('system:post:export')],
)
@ApiRateLimit(namespace=ApiNamespace.SYSTEM_POST_EXPORT, preset=ApiRateLimitPreset.USER_RESOURCE_EXPORT)
@Log(title='岗位管理', business_type=BusinessType.EXPORT)
async def export_system_post_list(
request: Request,
post_page_query: Annotated[PostPageQueryModel, Form()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
# 获取全量数据
post_query_result = await PostService.get_post_list_services(query_db, post_page_query, is_page=False)
post_export_result = await PostService.export_post_list_services(post_query_result)
logger.info('导出成功')
return ResponseUtil.streaming(data=bytes2file_response(post_export_result))
@@ -0,0 +1,400 @@
from datetime import datetime
from typing import Annotated
from fastapi import Form, Path, Query, Request, Response
from fastapi.responses import StreamingResponse
from pydantic_validation_decorator import ValidateFields
from sqlalchemy import ColumnElement
from sqlalchemy.ext.asyncio import AsyncSession
from common.annotation.cache_annotation import ApiCache, ApiCacheEvict
from common.annotation.log_annotation import Log
from common.annotation.rate_limit_annotation import ApiRateLimit, ApiRateLimitPreset
from common.aspect.data_scope import DataScopeDependency
from common.aspect.db_seesion import DBSessionDependency
from common.aspect.interface_auth import UserInterfaceAuthDependency
from common.aspect.pre_auth import CurrentUserDependency, PreAuthDependency
from common.constant import ApiGroup, ApiNamespace
from common.enums import BusinessType
from common.router import APIRouterPro
from common.vo import DataResponseModel, DynamicResponseModel, PageResponseModel, ResponseBaseModel
from module_admin.entity.do.dept_do import SysDept
from module_admin.entity.do.user_do import SysUser
from module_admin.entity.vo.dept_vo import DeptModel
from module_admin.entity.vo.role_vo import (
AddRoleModel,
DeleteRoleModel,
RoleDeptQueryModel,
RoleModel,
RolePageQueryModel,
)
from module_admin.entity.vo.user_vo import CrudUserRoleModel, CurrentUserModel, UserInfoModel, UserRolePageQueryModel
from module_admin.service.dept_service import DeptService
from module_admin.service.role_service import RoleService
from module_admin.service.user_service import UserService
from utils.common_util import bytes2file_response
from utils.log_util import logger
from utils.response_util import ResponseUtil
role_controller = APIRouterPro(
prefix='/system/role', order_num=4, tags=['系统管理-角色管理'], dependencies=[PreAuthDependency()]
)
@role_controller.get(
'/deptTree/{role_id}',
summary='获取自定义数据权限时可见的部门树接口',
description='用于自定义数据权限时获取当前用户可见的部门树',
response_model=DynamicResponseModel[RoleDeptQueryModel],
dependencies=[UserInterfaceAuthDependency('system:role:query')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_ROLE_DEPT_TREE)
async def get_system_role_dept_tree(
request: Request,
role_id: Annotated[int, Path(description='角色ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
dept_query_result = await DeptService.get_dept_tree_services(query_db, DeptModel(), data_scope_sql)
role_dept_query_result = await RoleService.get_role_dept_tree_services(query_db, role_id)
role_dept_query_result.depts = dept_query_result
logger.info('获取成功')
return ResponseUtil.success(model_content=role_dept_query_result)
@role_controller.get(
'/list',
summary='获取角色分页列表接口',
description='用于获取角色分页列表',
response_model=PageResponseModel[RoleModel],
dependencies=[UserInterfaceAuthDependency('system:role:list')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_ROLE_LIST)
async def get_system_role_list(
request: Request,
role_page_query: Annotated[RolePageQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
role_page_query_result = await RoleService.get_role_list_services(
query_db, role_page_query, data_scope_sql, is_page=True
)
logger.info('获取成功')
return ResponseUtil.success(model_content=role_page_query_result)
@role_controller.post(
'',
summary='新增角色接口',
description='用于新增角色',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:role:add')],
)
@ValidateFields(validate_model='add_role')
@ApiCacheEvict(namespaces=ApiGroup.ROLE_ENTITY_MUTATION)
@Log(title='角色管理', business_type=BusinessType.INSERT)
async def add_system_role(
request: Request,
add_role: AddRoleModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
add_role.create_by = current_user.user.user_name
add_role.create_time = datetime.now()
add_role.update_by = current_user.user.user_name
add_role.update_time = datetime.now()
add_role_result = await RoleService.add_role_services(query_db, add_role)
logger.info(add_role_result.message)
return ResponseUtil.success(msg=add_role_result.message)
@role_controller.put(
'',
summary='编辑角色接口',
description='用于编辑角色',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:role:edit')],
)
@ValidateFields(validate_model='edit_role')
@ApiCacheEvict(namespaces=ApiGroup.ROLE_PERMISSION_MUTATION)
@Log(title='角色管理', business_type=BusinessType.UPDATE)
async def edit_system_role(
request: Request,
edit_role: AddRoleModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
await RoleService.check_role_allowed_services(edit_role)
if not current_user.user.admin:
await RoleService.check_role_data_scope_services(query_db, str(edit_role.role_id), data_scope_sql)
edit_role.update_by = current_user.user.user_name
edit_role.update_time = datetime.now()
edit_role_result = await RoleService.edit_role_services(query_db, edit_role)
logger.info(edit_role_result.message)
return ResponseUtil.success(msg=edit_role_result.message)
@role_controller.put(
'/dataScope',
summary='编辑角色数据权限接口',
description='用于编辑角色数据权限',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:role:edit')],
)
@ApiCacheEvict(namespaces=ApiGroup.DATA_SCOPE_MUTATION)
@Log(title='角色管理', business_type=BusinessType.GRANT)
async def edit_system_role_datascope(
request: Request,
role_data_scope: AddRoleModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
await RoleService.check_role_allowed_services(role_data_scope)
if not current_user.user.admin:
await RoleService.check_role_data_scope_services(query_db, str(role_data_scope.role_id), data_scope_sql)
edit_role = AddRoleModel(
roleId=role_data_scope.role_id,
dataScope=role_data_scope.data_scope,
deptIds=role_data_scope.dept_ids,
deptCheckStrictly=role_data_scope.dept_check_strictly,
updateBy=current_user.user.user_name,
updateTime=datetime.now(),
)
role_data_scope_result = await RoleService.role_datascope_services(query_db, edit_role)
logger.info(role_data_scope_result.message)
return ResponseUtil.success(msg=role_data_scope_result.message)
@role_controller.delete(
'/{role_ids}',
summary='删除角色接口',
description='用于删除角色',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:role:remove')],
)
@ApiCacheEvict(namespaces=ApiGroup.ROLE_ENTITY_MUTATION)
@Log(title='角色管理', business_type=BusinessType.DELETE)
async def delete_system_role(
request: Request,
role_ids: Annotated[str, Path(description='需要删除的角色ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
role_id_list = role_ids.split(',') if role_ids else []
if role_id_list:
for role_id in role_id_list:
await RoleService.check_role_allowed_services(RoleModel(roleId=int(role_id)))
if not current_user.user.admin:
await RoleService.check_role_data_scope_services(query_db, role_id, data_scope_sql)
delete_role = DeleteRoleModel(roleIds=role_ids, updateBy=current_user.user.user_name, updateTime=datetime.now())
delete_role_result = await RoleService.delete_role_services(query_db, delete_role)
logger.info(delete_role_result.message)
return ResponseUtil.success(msg=delete_role_result.message)
@role_controller.get(
'/{role_id}',
summary='获取角色详情接口',
description='用于获取指定角色的详细信息',
response_model=DataResponseModel[RoleModel],
dependencies=[UserInterfaceAuthDependency('system:role:query')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_ROLE_DETAIL)
async def query_detail_system_role(
request: Request,
role_id: Annotated[int, Path(description='角色ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
if not current_user.user.admin:
await RoleService.check_role_data_scope_services(query_db, str(role_id), data_scope_sql)
role_detail_result = await RoleService.role_detail_services(query_db, role_id)
logger.info(f'获取role_id为{role_id}的信息成功')
return ResponseUtil.success(data=role_detail_result.model_dump(by_alias=True))
@role_controller.post(
'/export',
summary='导出角色列表接口',
description='用于导出当前符合查询条件的角色列表数据',
response_class=StreamingResponse,
responses={
200: {
'description': '流式返回角色列表excel文件',
'content': {
'application/octet-stream': {},
},
}
},
dependencies=[UserInterfaceAuthDependency('system:role:export')],
)
@ApiRateLimit(namespace=ApiNamespace.SYSTEM_ROLE_EXPORT, preset=ApiRateLimitPreset.USER_RESOURCE_EXPORT)
@Log(title='角色管理', business_type=BusinessType.EXPORT)
async def export_system_role_list(
request: Request,
role_page_query: Annotated[RolePageQueryModel, Form()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
# 获取全量数据
role_query_result = await RoleService.get_role_list_services(
query_db, role_page_query, data_scope_sql, is_page=False
)
role_export_result = await RoleService.export_role_list_services(role_query_result)
logger.info('导出成功')
return ResponseUtil.streaming(data=bytes2file_response(role_export_result))
@role_controller.put(
'/changeStatus',
summary='修改角色状态接口',
description='用于修改角色状态',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:role:edit')],
)
@ApiCacheEvict(namespaces=ApiGroup.ROLE_MUTATION)
@Log(title='角色管理', business_type=BusinessType.UPDATE)
async def reset_system_role_status(
request: Request,
change_role: AddRoleModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
await RoleService.check_role_allowed_services(change_role)
if not current_user.user.admin:
await RoleService.check_role_data_scope_services(query_db, str(change_role.role_id), data_scope_sql)
edit_role = AddRoleModel(
roleId=change_role.role_id,
status=change_role.status,
updateBy=current_user.user.user_name,
updateTime=datetime.now(),
type='status',
)
edit_role_result = await RoleService.edit_role_services(query_db, edit_role)
logger.info(edit_role_result.message)
return ResponseUtil.success(msg=edit_role_result.message)
@role_controller.get(
'/authUser/allocatedList',
summary='获取已分配用户分页列表接口',
description='用于获取指定角色已分配的用户分页列表',
response_model=PageResponseModel[UserInfoModel],
dependencies=[UserInterfaceAuthDependency('system:role:list')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_ROLE_ALLOCATED_USER_LIST)
async def get_system_allocated_user_list(
request: Request,
user_role: Annotated[UserRolePageQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
) -> Response:
role_user_allocated_page_query_result = await RoleService.get_role_user_allocated_list_services(
query_db, user_role, data_scope_sql, is_page=True
)
logger.info('获取成功')
return ResponseUtil.success(model_content=role_user_allocated_page_query_result)
@role_controller.get(
'/authUser/unallocatedList',
summary='获取未分配用户分页列表接口',
description='用于获取指定角色未分配的用户分页列表',
response_model=PageResponseModel[UserInfoModel],
dependencies=[UserInterfaceAuthDependency('system:role:list')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_ROLE_UNALLOCATED_USER_LIST)
async def get_system_unallocated_user_list(
request: Request,
user_role: Annotated[UserRolePageQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
) -> Response:
role_user_unallocated_page_query_result = await RoleService.get_role_user_unallocated_list_services(
query_db, user_role, data_scope_sql, is_page=True
)
logger.info('获取成功')
return ResponseUtil.success(model_content=role_user_unallocated_page_query_result)
@role_controller.put(
'/authUser/selectAll',
summary='分配用户给角色接口',
description='用于给指定角色分配用户',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:role:edit')],
)
@ApiRateLimit(namespace=ApiNamespace.SYSTEM_ROLE_AUTH_USER_SELECT_ALL, preset=ApiRateLimitPreset.USER_SECURITY_MUTATION)
@ApiCacheEvict(namespaces=ApiGroup.ROLE_MUTATION)
@Log(title='角色管理', business_type=BusinessType.GRANT)
async def add_system_role_user(
request: Request,
add_role_user: Annotated[CrudUserRoleModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
if not current_user.user.admin:
await RoleService.check_role_data_scope_services(query_db, str(add_role_user.role_id), data_scope_sql)
add_role_user_result = await UserService.add_user_role_services(query_db, add_role_user)
logger.info(add_role_user_result.message)
return ResponseUtil.success(msg=add_role_user_result.message)
@role_controller.put(
'/authUser/cancel',
summary='取消分配用户给角色接口',
description='用于取消指定用户分配给角色',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:role:edit')],
)
@ApiRateLimit(namespace=ApiNamespace.SYSTEM_ROLE_AUTH_USER_CANCEL, preset=ApiRateLimitPreset.USER_SECURITY_MUTATION)
@ApiCacheEvict(namespaces=ApiGroup.ROLE_MUTATION)
@Log(title='角色管理', business_type=BusinessType.GRANT)
async def cancel_system_role_user(
request: Request,
cancel_user_role: CrudUserRoleModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
cancel_user_role_result = await UserService.delete_user_role_services(query_db, cancel_user_role)
logger.info(cancel_user_role_result.message)
return ResponseUtil.success(msg=cancel_user_role_result.message)
@role_controller.put(
'/authUser/cancelAll',
summary='批量取消分配用户给角色接口',
description='用于批量取消用户分配给角色',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:role:edit')],
)
@ApiRateLimit(namespace=ApiNamespace.SYSTEM_ROLE_AUTH_USER_CANCEL_ALL, preset=ApiRateLimitPreset.USER_SECURITY_MUTATION)
@ApiCacheEvict(namespaces=ApiGroup.ROLE_MUTATION)
@Log(title='角色管理', business_type=BusinessType.GRANT)
async def batch_cancel_system_role_user(
request: Request,
batch_cancel_user_role: Annotated[CrudUserRoleModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
batch_cancel_user_role_result = await UserService.delete_user_role_services(query_db, batch_cancel_user_role)
logger.info(batch_cancel_user_role_result.message)
return ResponseUtil.success(msg=batch_cancel_user_role_result.message)
@@ -0,0 +1,32 @@
from fastapi import Request, Response
from common.annotation.cache_annotation import ApiCache
from common.aspect.interface_auth import UserInterfaceAuthDependency
from common.aspect.pre_auth import PreAuthDependency
from common.constant import ApiNamespace
from common.router import APIRouterPro
from common.vo import DataResponseModel
from module_admin.entity.vo.server_vo import ServerMonitorModel
from module_admin.service.server_service import ServerService
from utils.log_util import logger
from utils.response_util import ResponseUtil
server_controller = APIRouterPro(
prefix='/monitor/server', order_num=14, tags=['系统监控-服务监控'], dependencies=[PreAuthDependency()]
)
@server_controller.get(
'',
summary='获取服务器监控信息接口',
description='用于获取当前服务器的监控信息',
response_model=DataResponseModel[ServerMonitorModel],
dependencies=[UserInterfaceAuthDependency('monitor:server:list')],
)
@ApiCache(namespace=ApiNamespace.MONITOR_SERVER_INFO)
async def get_monitor_server_info(request: Request) -> Response:
# 获取全量数据
server_info_query_result = await ServerService.get_server_monitor_info()
logger.info('获取成功')
return ResponseUtil.success(data=server_info_query_result)
@@ -0,0 +1,78 @@
from fastapi import Request, Response
from common.annotation.rate_limit_annotation import ApiRateLimit, ApiRateLimitPreset
from common.aspect.interface_auth import UserInterfaceAuthDependency
from common.aspect.pre_auth import PreAuthDependency
from common.constant import ApiNamespace
from common.router import APIRouterPro
from common.vo import DataResponseModel
from module_admin.entity.vo.transport_crypto_vo import (
TransportCryptoFrontendConfigModel,
TransportCryptoMonitorModel,
TransportCryptoPublicKeyModel,
)
from module_admin.service.transport_crypto_service import TransportCryptoService
from utils.log_util import logger
from utils.response_util import ResponseUtil
transport_crypto_controller = APIRouterPro(prefix='/transport/crypto', order_num=15, tags=['传输加密模块'])
@transport_crypto_controller.get(
'/frontend-config',
summary='获取前端传输加密配置接口',
description='公开接口,用于向前端下发当前传输层加解密启用状态与运行模式,供前端统一跟随后端策略',
response_model=DataResponseModel[TransportCryptoFrontendConfigModel],
)
@ApiRateLimit(namespace=ApiNamespace.TRANSPORT_CRYPTO_FRONTEND_CONFIG, preset=ApiRateLimitPreset.ANON_PUBLIC_METADATA)
async def get_transport_frontend_config(request: Request) -> Response:
"""
获取当前前端传输层加解密运行配置
:param request: 当前请求对象
:return: 前端传输层加解密运行配置响应
"""
transport_frontend_config = await TransportCryptoService.get_transport_frontend_config_services()
logger.info('获取成功')
return ResponseUtil.success(data=transport_frontend_config)
@transport_crypto_controller.get(
'/public-key',
summary='获取传输加密公钥接口',
description='公开接口,用于向前端下发当前可用的传输层加密公钥,已配置匿名限流保护',
response_model=DataResponseModel[TransportCryptoPublicKeyModel],
)
@ApiRateLimit(namespace=ApiNamespace.TRANSPORT_CRYPTO_PUBLIC_KEY, preset=ApiRateLimitPreset.ANON_PUBLIC_METADATA)
async def get_transport_public_key(request: Request) -> Response:
"""
获取当前传输层加密公钥
:param request: 当前请求对象
:return: 公钥下发响应
"""
transport_public_key = await TransportCryptoService.get_transport_public_key_services()
logger.info('获取成功')
return ResponseUtil.success(data=transport_public_key)
@transport_crypto_controller.get(
'/monitor',
summary='获取传输层加解密监控信息接口',
description='用于获取基于Redis聚合的传输层加解密运行状态与统计信息',
response_model=DataResponseModel[TransportCryptoMonitorModel],
dependencies=[PreAuthDependency(), UserInterfaceAuthDependency('monitor:transportCrypto:list')],
)
async def get_transport_crypto_monitor_info(request: Request) -> Response:
"""
获取基于Redis聚合的传输层加解密监控信息
:param request: 当前请求对象
:return: 传输层加解密监控信息响应
"""
transport_crypto_monitor_info = await TransportCryptoService.get_transport_crypto_monitor_info_services(request)
logger.info('获取成功')
return ResponseUtil.success(data=transport_crypto_monitor_info)
@@ -0,0 +1,550 @@
import os
from datetime import datetime
from typing import Annotated, Literal
import aiofiles
from fastapi import File, Form, Path, Query, Request, Response, UploadFile
from fastapi.responses import StreamingResponse
from pydantic_validation_decorator import ValidateFields
from sqlalchemy import ColumnElement
from sqlalchemy.ext.asyncio import AsyncSession
from common.annotation.cache_annotation import ApiCache, ApiCacheEvict
from common.annotation.log_annotation import Log
from common.annotation.rate_limit_annotation import ApiRateLimit, ApiRateLimitBypassConfig, ApiRateLimitPreset
from common.aspect.data_scope import DataScopeDependency
from common.aspect.db_seesion import DBSessionDependency
from common.aspect.interface_auth import UserInterfaceAuthDependency
from common.aspect.pre_auth import CurrentUserDependency, PreAuthDependency
from common.constant import ApiGroup, ApiNamespace
from common.enums import BusinessType
from common.router import APIRouterPro
from common.vo import DataResponseModel, DynamicResponseModel, PageResponseModel, ResponseBaseModel
from config.env import UploadConfig
from module_admin.entity.do.dept_do import SysDept
from module_admin.entity.do.user_do import SysUser
from module_admin.entity.vo.dept_vo import DeptModel, DeptTreeModel
from module_admin.entity.vo.user_vo import (
AddUserModel,
AvatarModel,
CrudUserRoleModel,
CurrentUserModel,
DeleteUserModel,
EditUserModel,
ResetPasswordModel,
ResetUserModel,
UserDetailModel,
UserInfoModel,
UserModel,
UserPageQueryModel,
UserProfileModel,
UserRoleQueryModel,
UserRoleResponseModel,
UserRowModel,
)
from module_admin.service.dept_service import DeptService
from module_admin.service.role_service import RoleService
from module_admin.service.user_service import UserService
from utils.common_util import bytes2file_response
from utils.log_util import logger
from utils.pwd_util import PwdUtil
from utils.response_util import ResponseUtil
from utils.upload_util import UploadUtil
user_controller = APIRouterPro(
prefix='/system/user', order_num=3, tags=['系统管理-用户管理'], dependencies=[PreAuthDependency()]
)
@user_controller.get(
'/deptTree',
summary='获取部门树接口',
description='用于获取当前登录用户可见的部门树',
response_model=DataResponseModel[list[DeptTreeModel]],
dependencies=[UserInterfaceAuthDependency('system:user:list')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_USER_DEPT_TREE)
async def get_system_dept_tree(
request: Request,
query_db: Annotated[AsyncSession, DBSessionDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
dept_query_result = await DeptService.get_dept_tree_services(query_db, DeptModel(), data_scope_sql)
logger.info('获取成功')
return ResponseUtil.success(data=dept_query_result)
@user_controller.get(
'/list',
summary='获取用户分页列表接口',
description='用于获取用户分页列表',
response_model=PageResponseModel[UserRowModel],
dependencies=[UserInterfaceAuthDependency('system:user:list')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_USER_LIST)
async def get_system_user_list(
request: Request,
user_page_query: Annotated[UserPageQueryModel, Query()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
) -> Response:
# 获取分页数据
user_page_query_result = await UserService.get_user_list_services(
query_db, user_page_query, data_scope_sql, is_page=True
)
logger.info('获取成功')
return ResponseUtil.success(model_content=user_page_query_result)
@user_controller.post(
'',
summary='新增用户接口',
description='用于新增用户',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:user:add')],
)
@ValidateFields(validate_model='add_user')
@ApiCacheEvict(namespaces=ApiGroup.USER_ENTITY_MUTATION)
@Log(title='用户管理', business_type=BusinessType.INSERT)
async def add_system_user(
request: Request,
add_user: AddUserModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
dept_data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
role_data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
if not current_user.user.admin:
await DeptService.check_dept_data_scope_services(query_db, add_user.dept_id, dept_data_scope_sql)
await RoleService.check_role_data_scope_services(
query_db, ','.join([str(item) for item in add_user.role_ids]), role_data_scope_sql
)
add_user.password = PwdUtil.get_password_hash(add_user.password)
add_user.create_by = current_user.user.user_name
add_user.create_time = datetime.now()
add_user.update_by = current_user.user.user_name
add_user.update_time = datetime.now()
add_user_result = await UserService.add_user_services(query_db, add_user)
logger.info(add_user_result.message)
return ResponseUtil.success(msg=add_user_result.message)
@user_controller.put(
'',
summary='编辑用户接口',
description='用于编辑用户',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:user:edit')],
)
@ValidateFields(validate_model='edit_user')
@ApiCacheEvict(namespaces=ApiGroup.USER_PERMISSION_MUTATION)
@Log(title='用户管理', business_type=BusinessType.UPDATE)
async def edit_system_user(
request: Request,
edit_user: EditUserModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
user_data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
dept_data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
role_data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
await UserService.check_user_allowed_services(edit_user)
if not current_user.user.admin:
await UserService.check_user_data_scope_services(query_db, edit_user.user_id, user_data_scope_sql)
await DeptService.check_dept_data_scope_services(query_db, edit_user.dept_id, dept_data_scope_sql)
await RoleService.check_role_data_scope_services(
query_db, ','.join([str(item) for item in edit_user.role_ids]), role_data_scope_sql
)
edit_user.update_by = current_user.user.user_name
edit_user.update_time = datetime.now()
edit_user_result = await UserService.edit_user_services(query_db, edit_user)
logger.info(edit_user_result.message)
return ResponseUtil.success(msg=edit_user_result.message)
@user_controller.delete(
'/{user_ids}',
summary='删除用户接口',
description='用于删除用户',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:user:remove')],
)
@ApiCacheEvict(namespaces=ApiGroup.USER_ENTITY_MUTATION)
@Log(title='用户管理', business_type=BusinessType.DELETE)
async def delete_system_user(
request: Request,
user_ids: Annotated[str, Path(description='需要删除的用户ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
) -> Response:
user_id_list = user_ids.split(',') if user_ids else []
if user_id_list:
if current_user.user.user_id in list(map(int, user_id_list)):
logger.warning('当前登录用户不能删除')
return ResponseUtil.failure(msg='当前登录用户不能删除')
for user_id in user_id_list:
await UserService.check_user_allowed_services(UserModel(userId=int(user_id)))
if not current_user.user.admin:
await UserService.check_user_data_scope_services(query_db, int(user_id), data_scope_sql)
delete_user = DeleteUserModel(userIds=user_ids, updateBy=current_user.user.user_name, updateTime=datetime.now())
delete_user_result = await UserService.delete_user_services(query_db, delete_user)
logger.info(delete_user_result.message)
return ResponseUtil.success(msg=delete_user_result.message)
@user_controller.put(
'/resetPwd',
summary='重置用户密码接口',
description='用于重置用户密码',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:user:resetPwd')],
)
@ApiCacheEvict(namespaces=ApiGroup.USER_INFO_MUTATION)
@Log(title='用户管理', business_type=BusinessType.UPDATE)
async def reset_system_user_pwd(
request: Request,
reset_user: EditUserModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
) -> Response:
await UserService.check_user_allowed_services(reset_user)
if not current_user.user.admin:
await UserService.check_user_data_scope_services(query_db, reset_user.user_id, data_scope_sql)
edit_user = EditUserModel(
userId=reset_user.user_id,
password=PwdUtil.get_password_hash(reset_user.password),
pwdUpdateDate=datetime.now(),
updateBy=current_user.user.user_name,
updateTime=datetime.now(),
type='pwd',
)
edit_user_result = await UserService.edit_user_services(query_db, edit_user)
logger.info(edit_user_result.message)
return ResponseUtil.success(msg=edit_user_result.message)
@user_controller.put(
'/changeStatus',
summary='修改用户状态接口',
description='用于修改用户状态',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:user:edit')],
)
@ApiCacheEvict(namespaces=ApiGroup.USER_INFO_MUTATION)
@Log(title='用户管理', business_type=BusinessType.UPDATE)
async def change_system_user_status(
request: Request,
change_user: EditUserModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
) -> Response:
await UserService.check_user_allowed_services(change_user)
if not current_user.user.admin:
await UserService.check_user_data_scope_services(query_db, change_user.user_id, data_scope_sql)
edit_user = EditUserModel(
userId=change_user.user_id,
status=change_user.status,
updateBy=current_user.user.user_name,
updateTime=datetime.now(),
type='status',
)
edit_user_result = await UserService.edit_user_services(query_db, edit_user)
logger.info(edit_user_result.message)
return ResponseUtil.success(msg=edit_user_result.message)
@user_controller.get(
'/profile',
summary='获取用户个人信息接口',
description='用于获取当前登录用户的个人信息',
response_model=DynamicResponseModel[UserProfileModel],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_USER_PROFILE)
async def query_detail_system_user_profile(
request: Request,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
profile_user_result = await UserService.user_profile_services(query_db, current_user.user.user_id)
logger.info(f'获取user_id为{current_user.user.user_id}的信息成功')
return ResponseUtil.success(model_content=profile_user_result)
@user_controller.get(
'/{user_id}',
summary='获取用户详情接口',
description='用于获取指定用户的详情信息',
response_model=DynamicResponseModel[UserDetailModel],
dependencies=[UserInterfaceAuthDependency('system:user:query')],
)
@user_controller.get(
'/',
summary='获取用户岗位和角色列表接口',
description='用于获取当前登录用户可见的岗位和角色列表',
response_model=DynamicResponseModel[UserDetailModel],
dependencies=[UserInterfaceAuthDependency('system:user:query')],
)
@ApiCache(namespace=ApiNamespace.SYSTEM_USER_DETAIL)
async def query_detail_system_user(
request: Request,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
user_id: int | Literal[''] | None = '',
) -> Response:
if user_id and not current_user.user.admin:
await UserService.check_user_data_scope_services(query_db, user_id, data_scope_sql)
detail_user_result = await UserService.user_detail_services(query_db, user_id)
logger.info(f'获取user_id为{user_id}的信息成功')
return ResponseUtil.success(model_content=detail_user_result)
@user_controller.post(
'/profile/avatar',
summary='修改用户头像接口',
description='用于修改当前登录用户的头像',
response_model=DynamicResponseModel[AvatarModel],
)
@ApiRateLimit(namespace=ApiNamespace.SYSTEM_USER_PROFILE_AVATAR, preset=ApiRateLimitPreset.USER_RESOURCE_UPLOAD)
@ApiCacheEvict(namespaces=ApiGroup.USER_INFO_MUTATION)
@Log(title='个人信息', business_type=BusinessType.UPDATE)
async def change_system_user_profile_avatar(
request: Request,
avatarfile: Annotated[bytes, File()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
if avatarfile:
relative_path = (
f'avatar/{datetime.now().strftime("%Y")}/{datetime.now().strftime("%m")}/{datetime.now().strftime("%d")}'
)
dir_path = os.path.join(UploadConfig.UPLOAD_PATH, relative_path)
try:
os.makedirs(dir_path)
except FileExistsError:
pass
avatar_name = f'avatar_{datetime.now().strftime("%Y%m%d%H%M%S")}{UploadConfig.UPLOAD_MACHINE}{UploadUtil.generate_random_number()}.png'
avatar_path = os.path.join(dir_path, avatar_name)
async with aiofiles.open(avatar_path, 'wb') as f:
await f.write(avatarfile)
edit_user = EditUserModel(
userId=current_user.user.user_id,
avatar=f'{UploadConfig.UPLOAD_PREFIX}/{relative_path}/{avatar_name}',
updateBy=current_user.user.user_name,
updateTime=datetime.now(),
type='avatar',
)
edit_user_result = await UserService.edit_user_services(query_db, edit_user)
logger.info(edit_user_result.message)
return ResponseUtil.success(model_content=AvatarModel(imgUrl=edit_user.avatar), msg=edit_user_result.message)
return ResponseUtil.failure(msg='上传图片异常,请联系管理员')
@user_controller.put(
'/profile',
summary='修改用户个人信息接口',
description='用于修改当前登录用户的个人信息',
response_model=ResponseBaseModel,
)
@ApiCacheEvict(namespaces=ApiGroup.USER_INFO_MUTATION)
@Log(title='个人信息', business_type=BusinessType.UPDATE)
async def change_system_user_profile_info(
request: Request,
user_info: UserInfoModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
edit_user = EditUserModel(
**user_info.model_dump(exclude_unset=True, by_alias=True, exclude={'role_ids', 'post_ids'}),
userId=current_user.user.user_id,
userName=current_user.user.user_name,
updateBy=current_user.user.user_name,
updateTime=datetime.now(),
roleIds=current_user.user.role_ids.split(',') if current_user.user.role_ids else [],
postIds=current_user.user.post_ids.split(',') if current_user.user.post_ids else [],
role=current_user.user.role,
)
edit_user_result = await UserService.edit_user_services(query_db, edit_user)
logger.info(edit_user_result.message)
return ResponseUtil.success(msg=edit_user_result.message)
@user_controller.put(
'/profile/updatePwd',
summary='修改用户密码接口',
description='用于修改当前登录用户的密码',
response_model=ResponseBaseModel,
)
@ApiCacheEvict(namespaces=ApiGroup.USER_INFO_MUTATION)
@Log(title='个人信息', business_type=BusinessType.UPDATE)
async def reset_system_user_password(
request: Request,
reset_password: ResetPasswordModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
reset_user = ResetUserModel(
userId=current_user.user.user_id,
oldPassword=reset_password.old_password,
password=reset_password.new_password,
pwdUpdateDate=datetime.now(),
updateBy=current_user.user.user_name,
updateTime=datetime.now(),
)
reset_user_result = await UserService.reset_user_services(query_db, reset_user)
logger.info(reset_user_result.message)
return ResponseUtil.success(msg=reset_user_result.message)
@user_controller.post(
'/importData',
summary='批量导入用户接口',
description='用于批量导入用户数据',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:user:import')],
)
@ApiRateLimit(
namespace=ApiNamespace.SYSTEM_USER_IMPORT,
preset=ApiRateLimitPreset.USER_RESOURCE_IMPORT,
bypass=ApiRateLimitBypassConfig(roles=('admin',)),
)
@ApiCacheEvict(namespaces=ApiGroup.DATA_SCOPE_MUTATION)
@Log(title='用户管理', business_type=BusinessType.IMPORT)
async def batch_import_system_user(
request: Request,
file: Annotated[UploadFile, File(...)],
update_support: Annotated[bool, Query(alias='updateSupport')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
user_data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
dept_data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
batch_import_result = await UserService.batch_import_user_services(
request, query_db, file, update_support, current_user, user_data_scope_sql, dept_data_scope_sql
)
logger.info(batch_import_result.message)
return ResponseUtil.success(msg=batch_import_result.message)
@user_controller.post(
'/importTemplate',
summary='获取用户导入模板接口',
description='用于获取用户导入模板excel文件',
response_class=StreamingResponse,
responses={
200: {
'description': '流式返回导入用户模板excel文件',
'content': {
'application/octet-stream': {},
},
}
},
dependencies=[UserInterfaceAuthDependency('system:user:import')],
)
async def export_system_user_template(
request: Request, query_db: Annotated[AsyncSession, DBSessionDependency()]
) -> Response:
user_import_template_result = await UserService.get_user_import_template_services()
logger.info('获取成功')
return ResponseUtil.streaming(data=bytes2file_response(user_import_template_result))
@user_controller.post(
'/export',
summary='导出用户列表接口',
description='用于导出当前符合查询条件的用户列表数据',
response_class=StreamingResponse,
responses={
200: {
'description': '流式返回用户列表excel文件',
'content': {
'application/octet-stream': {},
},
}
},
dependencies=[UserInterfaceAuthDependency('system:user:export')],
)
@ApiRateLimit(namespace=ApiNamespace.SYSTEM_USER_EXPORT, preset=ApiRateLimitPreset.USER_RESOURCE_EXPORT)
@Log(title='用户管理', business_type=BusinessType.EXPORT)
async def export_system_user_list(
request: Request,
user_page_query: Annotated[UserPageQueryModel, Form()],
query_db: Annotated[AsyncSession, DBSessionDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
) -> Response:
# 获取全量数据
user_query_result = await UserService.get_user_list_services(
query_db, user_page_query, data_scope_sql, is_page=False
)
user_export_result = await UserService.export_user_list_services(user_query_result)
logger.info('导出成功')
return ResponseUtil.streaming(data=bytes2file_response(user_export_result))
@user_controller.get(
'/authRole/{user_id}',
summary='获取用户已分配角色列表接口',
description='用于获取指定用户已分配的角色列表',
response_model=DynamicResponseModel[UserRoleResponseModel],
dependencies=[UserInterfaceAuthDependency('system:user:query')],
)
async def get_system_allocated_role_list(
request: Request,
user_id: Annotated[int, Path(description='用户ID')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
user_role_query = UserRoleQueryModel(userId=user_id)
user_role_allocated_query_result = await UserService.get_user_role_allocated_list_services(
query_db, user_role_query
)
logger.info('获取成功')
return ResponseUtil.success(model_content=user_role_allocated_query_result)
@user_controller.put(
'/authRole',
summary='给用户分配角色接口',
description='用于给指定用户分配角色',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:user:edit')],
)
@ApiCacheEvict(namespaces=ApiGroup.USER_PERMISSION_MUTATION)
@Log(title='用户管理', business_type=BusinessType.GRANT)
async def update_system_role_user(
request: Request,
user_id: Annotated[int, Query(alias='userId')],
role_ids: Annotated[str, Query(alias='roleIds')],
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
user_data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
role_data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
if not current_user.user.admin:
await UserService.check_user_data_scope_services(query_db, user_id, user_data_scope_sql)
await RoleService.check_role_data_scope_services(query_db, role_ids, role_data_scope_sql)
add_user_role_result = await UserService.add_user_role_services(
query_db, CrudUserRoleModel(userId=user_id, roleIds=role_ids)
)
logger.info(add_user_role_result.message)
return ResponseUtil.success(msg=add_user_role_result.message)
@@ -0,0 +1,124 @@
from datetime import datetime, time
from typing import Any
from sqlalchemy import delete, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from common.vo import PageModel
from module_admin.entity.do.config_do import SysConfig
from module_admin.entity.vo.config_vo import ConfigModel, ConfigPageQueryModel
from utils.page_util import PageUtil
class ConfigDao:
"""
参数配置管理模块数据库操作层
"""
@classmethod
async def get_config_detail_by_id(cls, db: AsyncSession, config_id: int) -> SysConfig | None:
"""
根据参数配置id获取参数配置详细信息
:param db: orm对象
:param config_id: 参数配置id
:return: 参数配置信息对象
"""
config_info = (await db.execute(select(SysConfig).where(SysConfig.config_id == config_id))).scalars().first()
return config_info
@classmethod
async def get_config_detail_by_info(cls, db: AsyncSession, config: ConfigModel) -> SysConfig | None:
"""
根据参数配置参数获取参数配置信息
:param db: orm对象
:param config: 参数配置参数对象
:return: 参数配置信息对象
"""
config_info = (
(
await db.execute(
select(SysConfig).where(
SysConfig.config_key == config.config_key if config.config_key else True,
SysConfig.config_value == config.config_value if config.config_value else True,
)
)
)
.scalars()
.first()
)
return config_info
@classmethod
async def get_config_list(
cls, db: AsyncSession, query_object: ConfigPageQueryModel, is_page: bool = False
) -> PageModel | list[dict[str, Any]]:
"""
根据查询参数获取参数配置列表信息
:param db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 参数配置列表信息对象
"""
query = (
select(SysConfig)
.where(
SysConfig.config_name.like(f'%{query_object.config_name}%') if query_object.config_name else True,
SysConfig.config_key.like(f'%{query_object.config_key}%') if query_object.config_key else True,
SysConfig.config_type == query_object.config_type if query_object.config_type else True,
SysConfig.create_time.between(
datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)),
datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)),
)
if query_object.begin_time and query_object.end_time
else True,
)
.order_by(SysConfig.config_id)
.distinct()
)
config_list: PageModel | list[dict[str, Any]] = await PageUtil.paginate(
db, query, query_object.page_num, query_object.page_size, is_page
)
return config_list
@classmethod
async def add_config_dao(cls, db: AsyncSession, config: ConfigModel) -> SysConfig:
"""
新增参数配置数据库操作
:param db: orm对象
:param config: 参数配置对象
:return:
"""
db_config = SysConfig(**config.model_dump())
db.add(db_config)
await db.flush()
return db_config
@classmethod
async def edit_config_dao(cls, db: AsyncSession, config: dict) -> None:
"""
编辑参数配置数据库操作
:param db: orm对象
:param config: 需要更新的参数配置字典
:return:
"""
await db.execute(update(SysConfig), [config])
@classmethod
async def delete_config_dao(cls, db: AsyncSession, config: ConfigModel) -> None:
"""
删除参数配置数据库操作
:param db: orm对象
:param config: 参数配置对象
:return:
"""
await db.execute(delete(SysConfig).where(SysConfig.config_id.in_([config.config_id])))
@@ -0,0 +1,315 @@
from collections.abc import Sequence
from sqlalchemy import ColumnElement, bindparam, func, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.util import immutabledict
from module_admin.entity.do.dept_do import SysDept
from module_admin.entity.do.user_do import SysUser
from module_admin.entity.vo.dept_vo import DeptModel
class DeptDao:
"""
部门管理模块数据库操作层
"""
@classmethod
async def get_dept_by_id(cls, db: AsyncSession, dept_id: int) -> SysDept | None:
"""
根据部门id获取在用部门信息
:param db: orm对象
:param dept_id: 部门id
:return: 在用部门信息对象
"""
dept_info = (await db.execute(select(SysDept).where(SysDept.dept_id == dept_id))).scalars().first()
return dept_info
@classmethod
async def get_dept_detail_by_id(cls, db: AsyncSession, dept_id: int) -> SysDept | None:
"""
根据部门id获取部门详细信息
:param db: orm对象
:param dept_id: 部门id
:return: 部门信息对象
"""
dept_info = (
(await db.execute(select(SysDept).where(SysDept.dept_id == dept_id, SysDept.del_flag == '0')))
.scalars()
.first()
)
return dept_info
@classmethod
async def get_dept_detail_by_info(cls, db: AsyncSession, dept: DeptModel) -> SysDept | None:
"""
根据部门参数获取部门信息
:param db: orm对象
:param dept: 部门参数对象
:return: 部门信息对象
"""
dept_info = (
(
await db.execute(
select(SysDept).where(
SysDept.parent_id == dept.parent_id if dept.parent_id else True,
SysDept.dept_name == dept.dept_name if dept.dept_name else True,
SysDept.del_flag == '0',
)
)
)
.scalars()
.first()
)
return dept_info
@classmethod
async def get_dept_info_for_edit_option(
cls, db: AsyncSession, dept_info: DeptModel, data_scope_sql: ColumnElement
) -> Sequence[SysDept]:
"""
获取部门编辑对应的在用部门列表信息
:param db: orm对象
:param dept_info: 部门对象
:param data_scope_sql: 数据权限对应的查询sql语句
:return: 部门列表信息
"""
dept_result = (
(
await db.execute(
select(SysDept)
.where(
SysDept.dept_id != dept_info.dept_id,
~SysDept.dept_id.in_(
select(SysDept.dept_id).where(func.find_in_set(dept_info.dept_id, SysDept.ancestors))
),
SysDept.del_flag == '0',
SysDept.status == '0',
data_scope_sql,
)
.order_by(SysDept.order_num)
.distinct()
)
)
.scalars()
.all()
)
return dept_result
@classmethod
async def get_children_dept_dao(cls, db: AsyncSession, dept_id: int) -> Sequence[SysDept]:
"""
根据部门id查询当前部门的子部门列表信息
:param db: orm对象
:param dept_id: 部门id
:return: 子部门信息列表
"""
dept_result = (
(await db.execute(select(SysDept).where(func.find_in_set(dept_id, SysDept.ancestors)))).scalars().all()
)
return dept_result
@classmethod
async def get_dept_list_for_tree(
cls, db: AsyncSession, dept_info: DeptModel, data_scope_sql: ColumnElement
) -> Sequence[SysDept]:
"""
获取所有在用部门列表信息
:param db: orm对象
:param dept_info: 部门对象
:param data_scope_sql: 数据权限对应的查询sql语句
:return: 在用部门列表信息
"""
dept_result = (
(
await db.execute(
select(SysDept)
.where(
SysDept.status == '0',
SysDept.del_flag == '0',
SysDept.dept_name.like(f'%{dept_info.dept_name}%') if dept_info.dept_name else True,
data_scope_sql,
)
.order_by(SysDept.order_num)
.distinct()
)
)
.scalars()
.all()
)
return dept_result
@classmethod
async def get_dept_list(
cls, db: AsyncSession, page_object: DeptModel, data_scope_sql: ColumnElement
) -> Sequence[SysDept]:
"""
根据查询参数获取部门列表信息
:param db: orm对象
:param page_object: 不分页查询参数对象
:param data_scope_sql: 数据权限对应的查询sql语句
:return: 部门列表信息对象
"""
dept_result = (
(
await db.execute(
select(SysDept)
.where(
SysDept.del_flag == '0',
SysDept.dept_id == page_object.dept_id if page_object.dept_id is not None else True,
SysDept.status == page_object.status if page_object.status else True,
SysDept.dept_name.like(f'%{page_object.dept_name}%') if page_object.dept_name else True,
data_scope_sql,
)
.order_by(SysDept.order_num)
.distinct()
)
)
.scalars()
.all()
)
return dept_result
@classmethod
async def add_dept_dao(cls, db: AsyncSession, dept: DeptModel) -> SysDept:
"""
新增部门数据库操作
:param db: orm对象
:param dept: 部门对象
:return: 新增校验结果
"""
db_dept = SysDept(**dept.model_dump())
db.add(db_dept)
await db.flush()
return db_dept
@classmethod
async def edit_dept_dao(cls, db: AsyncSession, dept: dict) -> None:
"""
编辑部门数据库操作
:param db: orm对象
:param dept: 需要更新的部门字典
:return: 编辑校验结果
"""
await db.execute(update(SysDept), [dept])
@classmethod
async def update_dept_children_dao(cls, db: AsyncSession, update_dept: list) -> None:
"""
更新子部门信息
:param db: orm对象
:param update_dept: 需要更新的部门列表
:return:
"""
await db.execute(
update(SysDept)
.where(SysDept.dept_id == bindparam('dept_id'))
.values(
{
'dept_id': bindparam('dept_id'),
'ancestors': bindparam('ancestors'),
}
),
update_dept,
execution_options=immutabledict({'synchronize_session': None}),
)
@classmethod
async def update_dept_status_normal_dao(cls, db: AsyncSession, dept_id_list: list) -> None:
"""
批量更新部门状态为正常
:param db: orm对象
:param dept_id_list: 部门id列表
:return:
"""
await db.execute(update(SysDept).where(SysDept.dept_id.in_(dept_id_list)).values(status='0'))
@classmethod
async def delete_dept_dao(cls, db: AsyncSession, dept: DeptModel) -> None:
"""
删除部门数据库操作
:param db: orm对象
:param dept: 部门对象
:return:
"""
await db.execute(
update(SysDept)
.where(SysDept.dept_id == dept.dept_id)
.values(del_flag='2', update_by=dept.update_by, update_time=dept.update_time)
)
@classmethod
async def count_normal_children_dept_dao(cls, db: AsyncSession, dept_id: int) -> int | None:
"""
根据部门id查询查询所有子部门(正常状态)的数量
:param db: orm对象
:param dept_id: 部门id
:return: 所有子部门(正常状态)的数量
"""
normal_children_dept_count = (
await db.execute(
select(func.count('*'))
.select_from(SysDept)
.where(SysDept.status == '0', SysDept.del_flag == '0', func.find_in_set(dept_id, SysDept.ancestors))
)
).scalar()
return normal_children_dept_count
@classmethod
async def count_children_dept_dao(cls, db: AsyncSession, dept_id: int) -> int | None:
"""
根据部门id查询查询所有子部门(所有状态)的数量
:param db: orm对象
:param dept_id: 部门id
:return: 所有子部门(所有状态)的数量
"""
children_dept_count = (
await db.execute(
select(func.count('*'))
.select_from(SysDept)
.where(SysDept.del_flag == '0', SysDept.parent_id == dept_id)
.limit(1)
)
).scalar()
return children_dept_count
@classmethod
async def count_dept_user_dao(cls, db: AsyncSession, dept_id: int) -> int | None:
"""
根据部门id查询查询部门下的用户数量
:param db: orm对象
:param dept_id: 部门id
:return: 部门下的用户数量
"""
dept_user_count = (
await db.execute(
select(func.count('*')).select_from(SysUser).where(SysUser.dept_id == dept_id, SysUser.del_flag == '0')
)
).scalar()
return dept_user_count
@@ -0,0 +1,294 @@
from collections.abc import Sequence
from datetime import datetime, time
from typing import Any
from sqlalchemy import and_, delete, func, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from common.vo import PageModel
from module_admin.entity.do.dict_do import SysDictData, SysDictType
from module_admin.entity.vo.dict_vo import DictDataModel, DictDataPageQueryModel, DictTypeModel, DictTypePageQueryModel
from utils.page_util import PageUtil
from utils.time_format_util import list_format_datetime
class DictTypeDao:
"""
字典类型管理模块数据库操作层
"""
@classmethod
async def get_dict_type_detail_by_id(cls, db: AsyncSession, dict_id: int) -> SysDictType | None:
"""
根据字典类型id获取字典类型详细信息
:param db: orm对象
:param dict_id: 字典类型id
:return: 字典类型信息对象
"""
dict_type_info = (await db.execute(select(SysDictType).where(SysDictType.dict_id == dict_id))).scalars().first()
return dict_type_info
@classmethod
async def get_dict_type_detail_by_info(cls, db: AsyncSession, dict_type: DictTypeModel) -> SysDictType | None:
"""
根据字典类型参数获取字典类型信息
:param db: orm对象
:param dict_type: 字典类型参数对象
:return: 字典类型信息对象
"""
dict_type_info = (
(
await db.execute(
select(SysDictType).where(
SysDictType.dict_type == dict_type.dict_type if dict_type.dict_type else True,
SysDictType.dict_name == dict_type.dict_name if dict_type.dict_name else True,
)
)
)
.scalars()
.first()
)
return dict_type_info
@classmethod
async def get_all_dict_type(cls, db: AsyncSession) -> list[Any]:
"""
获取所有的字典类型信息
:param db: orm对象
:return: 字典类型信息列表对象
"""
dict_type_info = (await db.execute(select(SysDictType))).scalars().all()
return list_format_datetime(dict_type_info)
@classmethod
async def get_dict_type_list(
cls, db: AsyncSession, query_object: DictTypePageQueryModel, is_page: bool = False
) -> PageModel | list[dict[str, Any]]:
"""
根据查询参数获取字典类型列表信息
:param db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 字典类型列表信息对象
"""
query = (
select(SysDictType)
.where(
SysDictType.dict_name.like(f'%{query_object.dict_name}%') if query_object.dict_name else True,
SysDictType.dict_type.like(f'%{query_object.dict_type}%') if query_object.dict_type else True,
SysDictType.status == query_object.status if query_object.status else True,
SysDictType.create_time.between(
datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)),
datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)),
)
if query_object.begin_time and query_object.end_time
else True,
)
.order_by(SysDictType.dict_id)
.distinct()
)
dict_type_list: PageModel | list[dict[str, Any]] = await PageUtil.paginate(
db, query, query_object.page_num, query_object.page_size, is_page
)
return dict_type_list
@classmethod
async def add_dict_type_dao(cls, db: AsyncSession, dict_type: DictTypeModel) -> SysDictType:
"""
新增字典类型数据库操作
:param db: orm对象
:param dict_type: 字典类型对象
:return:
"""
db_dict_type = SysDictType(**dict_type.model_dump())
db.add(db_dict_type)
await db.flush()
return db_dict_type
@classmethod
async def edit_dict_type_dao(cls, db: AsyncSession, dict_type: dict) -> None:
"""
编辑字典类型数据库操作
:param db: orm对象
:param dict_type: 需要更新的字典类型字典
:return:
"""
await db.execute(update(SysDictType), [dict_type])
@classmethod
async def delete_dict_type_dao(cls, db: AsyncSession, dict_type: DictTypeModel) -> None:
"""
删除字典类型数据库操作
:param db: orm对象
:param dict_type: 字典类型对象
:return:
"""
await db.execute(delete(SysDictType).where(SysDictType.dict_id.in_([dict_type.dict_id])))
class DictDataDao:
"""
字典数据管理模块数据库操作层
"""
@classmethod
async def get_dict_data_detail_by_id(cls, db: AsyncSession, dict_code: int) -> SysDictData | None:
"""
根据字典数据id获取字典数据详细信息
:param db: orm对象
:param dict_code: 字典数据id
:return: 字典数据信息对象
"""
dict_data_info = (
(await db.execute(select(SysDictData).where(SysDictData.dict_code == dict_code))).scalars().first()
)
return dict_data_info
@classmethod
async def get_dict_data_detail_by_info(cls, db: AsyncSession, dict_data: DictDataModel) -> SysDictData | None:
"""
根据字典数据参数获取字典数据信息
:param db: orm对象
:param dict_data: 字典数据参数对象
:return: 字典数据信息对象
"""
dict_data_info = (
(
await db.execute(
select(SysDictData).where(
SysDictData.dict_type == dict_data.dict_type,
SysDictData.dict_label == dict_data.dict_label,
SysDictData.dict_value == dict_data.dict_value,
)
)
)
.scalars()
.first()
)
return dict_data_info
@classmethod
async def get_dict_data_list(
cls, db: AsyncSession, query_object: DictDataPageQueryModel, is_page: bool = False
) -> PageModel | list[dict[str, Any]]:
"""
根据查询参数获取字典数据列表信息
:param db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 字典数据列表信息对象
"""
query = (
select(SysDictData)
.where(
SysDictData.dict_type == query_object.dict_type if query_object.dict_type else True,
SysDictData.dict_label.like(f'%{query_object.dict_label}%') if query_object.dict_label else True,
SysDictData.status == query_object.status if query_object.status else True,
)
.order_by(SysDictData.dict_sort)
.distinct()
)
dict_data_list: PageModel | list[dict[str, Any]] = await PageUtil.paginate(
db, query, query_object.page_num, query_object.page_size, is_page
)
return dict_data_list
@classmethod
async def query_dict_data_list(cls, db: AsyncSession, dict_type: str) -> Sequence[SysDictData]:
"""
根据查询参数获取字典数据列表信息
:param db: orm对象
:param dict_type: 字典类型
:return: 字典数据列表信息对象
"""
dict_data_list = (
(
await db.execute(
select(SysDictData)
.select_from(SysDictType)
.where(SysDictType.dict_type == dict_type if dict_type else True, SysDictType.status == '0')
.join(
SysDictData,
and_(SysDictType.dict_type == SysDictData.dict_type, SysDictData.status == '0'),
isouter=True,
)
.order_by(SysDictData.dict_sort)
.distinct()
)
)
.scalars()
.all()
)
return dict_data_list
@classmethod
async def add_dict_data_dao(cls, db: AsyncSession, dict_data: DictDataModel) -> SysDictData:
"""
新增字典数据数据库操作
:param db: orm对象
:param dict_data: 字典数据对象
:return:
"""
db_data_type = SysDictData(**dict_data.model_dump())
db.add(db_data_type)
await db.flush()
return db_data_type
@classmethod
async def edit_dict_data_dao(cls, db: AsyncSession, dict_data: dict) -> None:
"""
编辑字典数据数据库操作
:param db: orm对象
:param dict_data: 需要更新的字典数据字典
:return:
"""
await db.execute(update(SysDictData), [dict_data])
@classmethod
async def delete_dict_data_dao(cls, db: AsyncSession, dict_data: DictDataModel) -> None:
"""
删除字典数据数据库操作
:param db: orm对象
:param dict_data: 字典数据对象
:return:
"""
await db.execute(delete(SysDictData).where(SysDictData.dict_code.in_([dict_data.dict_code])))
@classmethod
async def count_dict_data_dao(cls, db: AsyncSession, dict_type: str) -> int | None:
"""
根据字典类型查询字典类型关联的字典数据数量
:param db: orm对象
:param dict_type: 字典类型
:return: 字典类型关联的字典数据数量
"""
dict_data_count = (
await db.execute(select(func.count('*')).select_from(SysDictData).where(SysDictData.dict_type == dict_type))
).scalar()
return dict_data_count
@@ -0,0 +1,156 @@
from collections.abc import Sequence
from typing import Any
from sqlalchemy import delete, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from common.vo import PageModel
from module_admin.entity.do.job_do import SysJob
from module_admin.entity.vo.job_vo import JobModel, JobPageQueryModel
from utils.page_util import PageUtil
class JobDao:
"""
定时任务管理模块数据库操作层
"""
@classmethod
async def get_job_detail_by_id(cls, db: AsyncSession, job_id: int) -> SysJob | None:
"""
根据定时任务id获取定时任务详细信息
:param db: orm对象
:param job_id: 定时任务id
:return: 定时任务信息对象
"""
job_info = (await db.execute(select(SysJob).where(SysJob.job_id == job_id))).scalars().first()
return job_info
@classmethod
async def get_job_detail_by_info(cls, db: AsyncSession, job: JobModel) -> SysJob | None:
"""
根据定时任务参数获取定时任务信息
:param db: orm对象
:param job: 定时任务参数对象
:return: 定时任务信息对象
"""
job_info = (
(
await db.execute(
select(SysJob).where(
SysJob.job_name == job.job_name,
SysJob.job_group == job.job_group,
SysJob.job_executor == job.job_executor,
SysJob.invoke_target == job.invoke_target,
SysJob.job_args == job.job_args,
SysJob.job_kwargs == job.job_kwargs,
SysJob.cron_expression == job.cron_expression,
)
)
)
.scalars()
.first()
)
return job_info
@classmethod
async def get_job_list(
cls, db: AsyncSession, query_object: JobPageQueryModel, is_page: bool = False
) -> PageModel | list[dict[str, Any]]:
"""
根据查询参数获取定时任务列表信息
:param db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 定时任务列表信息对象
"""
query = (
select(SysJob)
.where(
SysJob.job_name.like(f'%{query_object.job_name}%') if query_object.job_name else True,
SysJob.job_group == query_object.job_group if query_object.job_group else True,
SysJob.status == query_object.status if query_object.status else True,
)
.order_by(SysJob.job_id)
.distinct()
)
job_list: PageModel | list[dict[str, Any]] = await PageUtil.paginate(
db, query, query_object.page_num, query_object.page_size, is_page
)
return job_list
@classmethod
async def get_job_list_for_scheduler(cls, db: AsyncSession) -> Sequence[SysJob]:
"""
获取定时任务列表信息
:param db: orm对象
:return: 定时任务列表信息对象
"""
job_list = (await db.execute(select(SysJob).where(SysJob.status == '0').distinct())).scalars().all()
return job_list
@classmethod
async def get_all_job_list_for_scheduler(cls, db: AsyncSession) -> Sequence[SysJob]:
"""
获取全部定时任务列表信息
:param db: orm对象
:return: 定时任务列表信息对象
"""
job_list = (await db.execute(select(SysJob).distinct())).scalars().all()
return job_list
@classmethod
async def add_job_dao(cls, db: AsyncSession, job: JobModel) -> SysJob:
"""
新增定时任务数据库操作
:param db: orm对象
:param job: 定时任务对象
:return:
"""
db_job = SysJob(**job.model_dump())
db.add(db_job)
await db.flush()
return db_job
@classmethod
async def edit_job_dao(cls, db: AsyncSession, job: dict, old_job: JobModel) -> None:
"""
编辑定时任务数据库操作
:param db: orm对象
:param job: 需要更新的定时任务字典
:param old_job: 原定时任务对象
:return:
"""
await db.execute(
update(SysJob)
.where(
SysJob.job_id == old_job.job_id,
SysJob.job_name == old_job.job_name,
SysJob.job_group == old_job.job_group,
)
.values(**job)
)
@classmethod
async def delete_job_dao(cls, db: AsyncSession, job: JobModel) -> None:
"""
删除定时任务数据库操作
:param db: orm对象
:param job: 定时任务对象
:return:
"""
await db.execute(delete(SysJob).where(SysJob.job_id.in_([job.job_id])))
@@ -0,0 +1,87 @@
from datetime import datetime, time
from typing import Any
from sqlalchemy import delete, desc, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Session
from common.vo import PageModel
from module_admin.entity.do.job_do import SysJobLog
from module_admin.entity.vo.job_vo import JobLogModel, JobLogPageQueryModel
from utils.page_util import PageUtil
class JobLogDao:
"""
定时任务日志管理模块数据库操作层
"""
@classmethod
async def get_job_log_list(
cls, db: AsyncSession, query_object: JobLogPageQueryModel, is_page: bool = False
) -> PageModel | list[dict[str, Any]]:
"""
根据查询参数获取定时任务日志列表信息
:param db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 定时任务日志列表信息对象
"""
query = (
select(SysJobLog)
.where(
SysJobLog.job_name.like(f'%{query_object.job_name}%') if query_object.job_name else True,
SysJobLog.job_group == query_object.job_group if query_object.job_group else True,
SysJobLog.status == query_object.status if query_object.status else True,
SysJobLog.create_time.between(
datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)),
datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)),
)
if query_object.begin_time and query_object.end_time
else True,
)
.order_by(desc(SysJobLog.create_time))
.distinct()
)
job_log_list: PageModel | list[dict[str, Any]] = await PageUtil.paginate(
db, query, query_object.page_num, query_object.page_size, is_page
)
return job_log_list
@classmethod
def add_job_log_dao(cls, db: Session, job_log: JobLogModel) -> SysJobLog:
"""
新增定时任务日志数据库操作
:param db: orm对象
:param job_log: 定时任务日志对象
:return:
"""
db_job_log = SysJobLog(**job_log.model_dump())
db.add(db_job_log)
db.flush()
return db_job_log
@classmethod
async def delete_job_log_dao(cls, db: AsyncSession, job_log: JobLogModel) -> None:
"""
删除定时任务日志数据库操作
:param db: orm对象
:param job_log: 定时任务日志对象
:return:
"""
await db.execute(delete(SysJobLog).where(SysJobLog.job_log_id.in_([job_log.job_log_id])))
@classmethod
async def clear_job_log_dao(cls, db: AsyncSession) -> None:
"""
清除定时任务日志数据库操作
:param db: orm对象
:return:
"""
await db.execute(delete(SysJobLog))
@@ -0,0 +1,183 @@
from datetime import datetime, time
from typing import Any
from sqlalchemy import asc, delete, desc, select
from sqlalchemy.ext.asyncio import AsyncSession
from common.vo import PageModel
from module_admin.entity.do.log_do import SysLogininfor, SysOperLog
from module_admin.entity.vo.log_vo import LogininforModel, LoginLogPageQueryModel, OperLogModel, OperLogPageQueryModel
from utils.common_util import SnakeCaseUtil
from utils.page_util import PageUtil
from utils.time_format_util import TimeFormatUtil
class OperationLogDao:
"""
操作日志管理模块数据库操作层
"""
@classmethod
async def get_operation_log_list(
cls, db: AsyncSession, query_object: OperLogPageQueryModel, is_page: bool = False
) -> PageModel | list[dict[str, Any]]:
"""
根据查询参数获取操作日志列表信息
:param db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 操作日志列表信息对象
"""
if query_object.is_asc == 'ascending':
order_by_column = asc(getattr(SysOperLog, SnakeCaseUtil.camel_to_snake(query_object.order_by_column), None))
elif query_object.is_asc == 'descending':
order_by_column = desc(
getattr(SysOperLog, SnakeCaseUtil.camel_to_snake(query_object.order_by_column), None)
)
else:
order_by_column = desc(SysOperLog.oper_time)
query = (
select(SysOperLog)
.where(
SysOperLog.title.like(f'%{query_object.title}%') if query_object.title else True,
SysOperLog.oper_name.like(f'%{query_object.oper_name}%') if query_object.oper_name else True,
SysOperLog.business_type == query_object.business_type if query_object.business_type else True,
SysOperLog.status == query_object.status if query_object.status else True,
SysOperLog.oper_time.between(
datetime.combine(TimeFormatUtil.parse_date(query_object.begin_time), time(00, 00, 00)),
datetime.combine(TimeFormatUtil.parse_date(query_object.end_time), time(23, 59, 59)),
)
if query_object.begin_time and query_object.end_time
else True,
)
.distinct()
.order_by(order_by_column)
)
operation_log_list: PageModel | list[dict[str, Any]] = await PageUtil.paginate(
db, query, query_object.page_num, query_object.page_size, is_page
)
return operation_log_list
@classmethod
async def add_operation_log_dao(cls, db: AsyncSession, operation_log: OperLogModel) -> SysOperLog:
"""
新增操作日志数据库操作
:param db: orm对象
:param operation_log: 操作日志对象
:return: 新增校验结果
"""
db_operation_log = SysOperLog(**operation_log.model_dump())
db.add(db_operation_log)
await db.flush()
return db_operation_log
@classmethod
async def delete_operation_log_dao(cls, db: AsyncSession, operation_log: OperLogModel) -> None:
"""
删除操作日志数据库操作
:param db: orm对象
:param operation_log: 操作日志对象
:return:
"""
await db.execute(delete(SysOperLog).where(SysOperLog.oper_id.in_([operation_log.oper_id])))
@classmethod
async def clear_operation_log_dao(cls, db: AsyncSession) -> None:
"""
清除操作日志数据库操作
:param db: orm对象
:return:
"""
await db.execute(delete(SysOperLog))
class LoginLogDao:
"""
登录日志管理模块数据库操作层
"""
@classmethod
async def get_login_log_list(
cls, db: AsyncSession, query_object: LoginLogPageQueryModel, is_page: bool = False
) -> PageModel | list[dict[str, Any]]:
"""
根据查询参数获取登录日志列表信息
:param db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 登录日志列表信息对象
"""
if query_object.is_asc == 'ascending':
order_by_column = asc(
getattr(SysLogininfor, SnakeCaseUtil.camel_to_snake(query_object.order_by_column), None)
)
elif query_object.is_asc == 'descending':
order_by_column = desc(
getattr(SysLogininfor, SnakeCaseUtil.camel_to_snake(query_object.order_by_column), None)
)
else:
order_by_column = desc(SysLogininfor.login_time)
query = (
select(SysLogininfor)
.where(
SysLogininfor.ipaddr.like(f'%{query_object.ipaddr}%') if query_object.ipaddr else True,
SysLogininfor.user_name.like(f'%{query_object.user_name}%') if query_object.user_name else True,
SysLogininfor.status == query_object.status if query_object.status else True,
SysLogininfor.login_time.between(
datetime.combine(TimeFormatUtil.parse_date(query_object.begin_time), time(00, 00, 00)),
datetime.combine(TimeFormatUtil.parse_date(query_object.end_time), time(23, 59, 59)),
)
if query_object.begin_time and query_object.end_time
else True,
)
.distinct()
.order_by(order_by_column)
)
login_log_list: PageModel | list[dict[str, Any]] = await PageUtil.paginate(
db, query, query_object.page_num, query_object.page_size, is_page
)
return login_log_list
@classmethod
async def add_login_log_dao(cls, db: AsyncSession, login_log: LogininforModel) -> SysLogininfor:
"""
新增登录日志数据库操作
:param db: orm对象
:param login_log: 登录日志对象
:return: 新增校验结果
"""
db_login_log = SysLogininfor(**login_log.model_dump())
db.add(db_login_log)
await db.flush()
return db_login_log
@classmethod
async def delete_login_log_dao(cls, db: AsyncSession, login_log: LogininforModel) -> None:
"""
删除登录日志数据库操作
:param db: orm对象
:param login_log: 登录日志对象
:return:
"""
await db.execute(delete(SysLogininfor).where(SysLogininfor.info_id.in_([login_log.info_id])))
@classmethod
async def clear_login_log_dao(cls, db: AsyncSession) -> None:
"""
清除登录日志数据库操作
:param db: orm对象
:return:
"""
await db.execute(delete(SysLogininfor))
@@ -0,0 +1,29 @@
from sqlalchemy import Row, and_, select
from sqlalchemy.ext.asyncio import AsyncSession
from module_admin.entity.do.dept_do import SysDept
from module_admin.entity.do.user_do import SysUser
async def login_by_account(db: AsyncSession, user_name: str) -> Row[tuple[SysUser, SysDept]] | None:
"""
根据用户名查询用户信息
:param db: orm对象
:param user_name: 用户名
:return: 用户对象
"""
user = (
await db.execute(
select(SysUser, SysDept)
.where(SysUser.user_name == user_name, SysUser.del_flag == '0')
.join(
SysDept,
and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == '0', SysDept.del_flag == '0'),
isouter=True,
)
.distinct()
)
).first()
return user
@@ -0,0 +1,228 @@
from collections.abc import Sequence
from sqlalchemy import and_, delete, func, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from module_admin.entity.do.menu_do import SysMenu
from module_admin.entity.do.role_do import SysRole, SysRoleMenu
from module_admin.entity.do.user_do import SysUser, SysUserRole
from module_admin.entity.vo.menu_vo import MenuModel, MenuQueryModel
class MenuDao:
"""
菜单管理模块数据库操作层
"""
@classmethod
async def get_menu_detail_by_id(cls, db: AsyncSession, menu_id: int) -> SysMenu | None:
"""
根据菜单id获取菜单详细信息
:param db: orm对象
:param menu_id: 菜单id
:return: 菜单信息对象
"""
menu_info = (await db.execute(select(SysMenu).where(SysMenu.menu_id == menu_id))).scalars().first()
return menu_info
@classmethod
async def get_menu_detail_by_info(cls, db: AsyncSession, menu: MenuModel) -> SysMenu | None:
"""
根据菜单参数获取菜单信息
:param db: orm对象
:param menu: 菜单参数对象
:return: 菜单信息对象
"""
menu_info = (
(
await db.execute(
select(SysMenu).where(
SysMenu.parent_id == menu.parent_id if menu.parent_id else True,
SysMenu.menu_name == menu.menu_name if menu.menu_name else True,
SysMenu.menu_type == menu.menu_type if menu.menu_type else True,
)
)
)
.scalars()
.first()
)
return menu_info
@classmethod
async def get_menu_list_for_tree(cls, db: AsyncSession, user_id: int, role: list) -> Sequence[SysMenu]:
"""
根据角色信息获取所有在用菜单列表信息
:param db: orm对象
:param user_id: 用户id
:param role: 用户角色列表信息
:return: 菜单列表信息
"""
role_id_list = [item.role_id for item in role]
if 1 in role_id_list:
menu_query_all = (
(await db.execute(select(SysMenu).where(SysMenu.status == '0').order_by(SysMenu.order_num).distinct()))
.scalars()
.all()
)
else:
menu_query_all = (
(
await db.execute(
select(SysMenu)
.select_from(SysUser)
.where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id)
.join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True)
.join(
SysRole,
and_(
SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0'
),
isouter=True,
)
.join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True)
.join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == '0'))
.order_by(SysMenu.order_num)
.distinct()
)
)
.scalars()
.all()
)
return menu_query_all
@classmethod
async def get_menu_list(
cls, db: AsyncSession, page_object: MenuQueryModel, user_id: int, role: list
) -> Sequence[SysMenu]:
"""
根据查询参数获取菜单列表信息
:param db: orm对象
:param page_object: 不分页查询参数对象
:param user_id: 用户id
:param role: 用户角色列表
:return: 菜单列表信息对象
"""
role_id_list = [item.role_id for item in role]
if 1 in role_id_list:
menu_query_all = (
(
await db.execute(
select(SysMenu)
.where(
SysMenu.status == page_object.status if page_object.status else True,
SysMenu.menu_name.like(f'%{page_object.menu_name}%') if page_object.menu_name else True,
)
.order_by(SysMenu.order_num)
.distinct()
)
)
.scalars()
.all()
)
else:
menu_query_all = (
(
await db.execute(
select(SysMenu)
.select_from(SysUser)
.where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id)
.join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True)
.join(
SysRole,
and_(
SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0'
),
isouter=True,
)
.join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True)
.join(
SysMenu,
and_(
SysRoleMenu.menu_id == SysMenu.menu_id,
SysMenu.status == page_object.status if page_object.status else True,
SysMenu.menu_name.like(f'%{page_object.menu_name}%') if page_object.menu_name else True,
),
)
.order_by(SysMenu.order_num)
.distinct()
)
)
.scalars()
.all()
)
return menu_query_all
@classmethod
async def add_menu_dao(cls, db: AsyncSession, menu: MenuModel) -> SysMenu:
"""
新增菜单数据库操作
:param db: orm对象
:param menu: 菜单对象
:return:
"""
db_menu = SysMenu(**menu.model_dump())
db.add(db_menu)
await db.flush()
return db_menu
@classmethod
async def edit_menu_dao(cls, db: AsyncSession, menu: dict) -> None:
"""
编辑菜单数据库操作
:param db: orm对象
:param menu: 需要更新的菜单字典
:return:
"""
await db.execute(update(SysMenu), [menu])
@classmethod
async def delete_menu_dao(cls, db: AsyncSession, menu: MenuModel) -> None:
"""
删除菜单数据库操作
:param db: orm对象
:param menu: 菜单对象
:return:
"""
await db.execute(delete(SysMenu).where(SysMenu.menu_id.in_([menu.menu_id])))
@classmethod
async def has_child_by_menu_id_dao(cls, db: AsyncSession, menu_id: int) -> int | None:
"""
根据菜单id查询菜单关联子菜单的数量
:param db: orm对象
:param menu_id: 菜单id
:return: 菜单关联子菜单的数量
"""
menu_count = (
await db.execute(select(func.count('*')).select_from(SysMenu).where(SysMenu.parent_id == menu_id))
).scalar()
return menu_count
@classmethod
async def check_menu_exist_role_dao(cls, db: AsyncSession, menu_id: int) -> int | None:
"""
根据菜单id查询菜单关联角色数量
:param db: orm对象
:param menu_id: 菜单id
:return: 菜单关联角色数量
"""
role_count = (
await db.execute(select(func.count('*')).select_from(SysRoleMenu).where(SysRoleMenu.menu_id == menu_id))
).scalar()
return role_count
@@ -0,0 +1,125 @@
from datetime import datetime, time
from typing import Any
from sqlalchemy import delete, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from common.vo import PageModel
from module_admin.entity.do.notice_do import SysNotice
from module_admin.entity.vo.notice_vo import NoticeModel, NoticePageQueryModel
from utils.page_util import PageUtil
class NoticeDao:
"""
通知公告管理模块数据库操作层
"""
@classmethod
async def get_notice_detail_by_id(cls, db: AsyncSession, notice_id: int) -> SysNotice | None:
"""
根据通知公告id获取通知公告详细信息
:param db: orm对象
:param notice_id: 通知公告id
:return: 通知公告信息对象
"""
notice_info = (await db.execute(select(SysNotice).where(SysNotice.notice_id == notice_id))).scalars().first()
return notice_info
@classmethod
async def get_notice_detail_by_info(cls, db: AsyncSession, notice: NoticeModel) -> SysNotice | None:
"""
根据通知公告参数获取通知公告信息
:param db: orm对象
:param notice: 通知公告参数对象
:return: 通知公告信息对象
"""
notice_info = (
(
await db.execute(
select(SysNotice).where(
SysNotice.notice_title == notice.notice_title,
SysNotice.notice_type == notice.notice_type,
SysNotice.notice_content == notice.notice_content,
)
)
)
.scalars()
.first()
)
return notice_info
@classmethod
async def get_notice_list(
cls, db: AsyncSession, query_object: NoticePageQueryModel, is_page: bool = False
) -> PageModel | list[dict[str, Any]]:
"""
根据查询参数获取通知公告列表信息
:param db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 通知公告列表信息对象
"""
query = (
select(SysNotice)
.where(
SysNotice.notice_title.like(f'%{query_object.notice_title}%') if query_object.notice_title else True,
SysNotice.create_by.like(f'%{query_object.create_by}%') if query_object.create_by else True,
SysNotice.notice_type == query_object.notice_type if query_object.notice_type else True,
SysNotice.create_time.between(
datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)),
datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)),
)
if query_object.begin_time and query_object.end_time
else True,
)
.order_by(SysNotice.notice_id)
.distinct()
)
notice_list: PageModel | list[dict[str, Any]] = await PageUtil.paginate(
db, query, query_object.page_num, query_object.page_size, is_page
)
return notice_list
@classmethod
async def add_notice_dao(cls, db: AsyncSession, notice: NoticeModel) -> SysNotice:
"""
新增通知公告数据库操作
:param db: orm对象
:param notice: 通知公告对象
:return:
"""
db_notice = SysNotice(**notice.model_dump())
db.add(db_notice)
await db.flush()
return db_notice
@classmethod
async def edit_notice_dao(cls, db: AsyncSession, notice: dict) -> None:
"""
编辑通知公告数据库操作
:param db: orm对象
:param notice: 需要更新的通知公告字典
:return:
"""
await db.execute(update(SysNotice), [notice])
@classmethod
async def delete_notice_dao(cls, db: AsyncSession, notice: NoticeModel) -> None:
"""
删除通知公告数据库操作
:param db: orm对象
:param notice: 通知公告对象
:return:
"""
await db.execute(delete(SysNotice).where(SysNotice.notice_id.in_([notice.notice_id])))
@@ -0,0 +1,151 @@
from typing import Any
from sqlalchemy import delete, func, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from common.vo import PageModel
from module_admin.entity.do.post_do import SysPost
from module_admin.entity.do.user_do import SysUserPost
from module_admin.entity.vo.post_vo import PostModel, PostPageQueryModel
from utils.page_util import PageUtil
class PostDao:
"""
岗位管理模块数据库操作层
"""
@classmethod
async def get_post_by_id(cls, db: AsyncSession, post_id: int) -> SysPost | None:
"""
根据岗位id获取在用岗位详细信息
:param db: orm对象
:param post_id: 岗位id
:return: 在用岗位信息对象
"""
post_info = (
(await db.execute(select(SysPost).where(SysPost.post_id == post_id, SysPost.status == '0')))
.scalars()
.first()
)
return post_info
@classmethod
async def get_post_detail_by_id(cls, db: AsyncSession, post_id: int) -> SysPost | None:
"""
根据岗位id获取岗位详细信息
:param db: orm对象
:param post_id: 岗位id
:return: 岗位信息对象
"""
post_info = (await db.execute(select(SysPost).where(SysPost.post_id == post_id))).scalars().first()
return post_info
@classmethod
async def get_post_detail_by_info(cls, db: AsyncSession, post: PostModel) -> SysPost | None:
"""
根据岗位参数获取岗位信息
:param db: orm对象
:param post: 岗位参数对象
:return: 岗位信息对象
"""
post_info = (
(
await db.execute(
select(SysPost).where(
SysPost.post_name == post.post_name if post.post_name else True,
SysPost.post_code == post.post_code if post.post_code else True,
SysPost.post_sort == post.post_sort if post.post_sort else True,
)
)
)
.scalars()
.first()
)
return post_info
@classmethod
async def get_post_list(
cls, db: AsyncSession, query_object: PostPageQueryModel, is_page: bool = False
) -> PageModel | list[dict[str, Any]]:
"""
根据查询参数获取岗位列表信息
:param db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 岗位列表信息对象
"""
query = (
select(SysPost)
.where(
SysPost.post_code.like(f'%{query_object.post_code}%') if query_object.post_code else True,
SysPost.post_name.like(f'%{query_object.post_name}%') if query_object.post_name else True,
SysPost.status == query_object.status if query_object.status else True,
)
.order_by(SysPost.post_sort)
.distinct()
)
post_list: PageModel | list[dict[str, Any]] = await PageUtil.paginate(
db, query, query_object.page_num, query_object.page_size, is_page
)
return post_list
@classmethod
async def add_post_dao(cls, db: AsyncSession, post: PostModel) -> SysPost:
"""
新增岗位数据库操作
:param db: orm对象
:param post: 岗位对象
:return:
"""
db_post = SysPost(**post.model_dump())
db.add(db_post)
await db.flush()
return db_post
@classmethod
async def edit_post_dao(cls, db: AsyncSession, post: dict) -> None:
"""
编辑岗位数据库操作
:param db: orm对象
:param post: 需要更新的岗位字典
:return:
"""
await db.execute(update(SysPost), [post])
@classmethod
async def delete_post_dao(cls, db: AsyncSession, post: PostModel) -> None:
"""
删除岗位数据库操作
:param db: orm对象
:param post: 岗位对象
:return:
"""
await db.execute(delete(SysPost).where(SysPost.post_id.in_([post.post_id])))
@classmethod
async def count_user_post_dao(cls, db: AsyncSession, post_id: int) -> int | None:
"""
根据岗位id查询岗位关联的用户数量
:param db: orm对象
:param post_id: 岗位id
:return: 岗位关联的用户数量
"""
user_post_count = (
await db.execute(select(func.count('*')).select_from(SysUserPost).where(SysUserPost.post_id == post_id))
).scalar()
return user_post_count
@@ -0,0 +1,345 @@
from collections.abc import Sequence
from datetime import datetime, time
from typing import Any
from sqlalchemy import ColumnElement, and_, delete, desc, func, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from common.vo import PageModel
from module_admin.entity.do.dept_do import SysDept
from module_admin.entity.do.menu_do import SysMenu
from module_admin.entity.do.role_do import SysRole, SysRoleDept, SysRoleMenu
from module_admin.entity.do.user_do import SysUser, SysUserRole
from module_admin.entity.vo.role_vo import RoleDeptModel, RoleMenuModel, RoleModel, RolePageQueryModel
from utils.page_util import PageUtil
class RoleDao:
"""
角色管理模块数据库操作层
"""
@classmethod
async def get_role_by_name(cls, db: AsyncSession, role_name: str) -> SysRole | None:
"""
根据角色名获取在用角色信息
:param db: orm对象
:param role_name: 角色名
:return: 当前角色名的角色信息对象
"""
query_role_info = (
(
await db.execute(
select(SysRole)
.where(SysRole.status == '0', SysRole.del_flag == '0', SysRole.role_name == role_name)
.order_by(desc(SysRole.create_time))
.distinct()
)
)
.scalars()
.first()
)
return query_role_info
@classmethod
async def get_role_by_info(cls, db: AsyncSession, role: RoleModel) -> SysRole | None:
"""
根据角色参数获取角色信息
:param db: orm对象
:param role: 角色参数
:return: 当前角色参数的角色信息对象
"""
query_role_info = (
(
await db.execute(
select(SysRole)
.where(
SysRole.del_flag == '0',
SysRole.role_name == role.role_name if role.role_name else True,
SysRole.role_key == role.role_key if role.role_key else True,
)
.order_by(desc(SysRole.create_time))
.distinct()
)
)
.scalars()
.first()
)
return query_role_info
@classmethod
async def get_role_by_id(cls, db: AsyncSession, role_id: int) -> SysRole | None:
"""
根据角色id获取在用角色信息
:param db: orm对象
:param role_id: 角色id
:return: 当前角色id的角色信息对象
"""
role_info = (
(
await db.execute(
select(SysRole).where(SysRole.role_id == role_id, SysRole.status == '0', SysRole.del_flag == '0')
)
)
.scalars()
.first()
)
return role_info
@classmethod
async def get_role_detail_by_id(cls, db: AsyncSession, role_id: int) -> SysRole | None:
"""
根据role_id获取角色详细信息
:param db: orm对象
:param role_id: 角色id
:return: 当前role_id的角色信息对象
"""
query_role_info = (
(await db.execute(select(SysRole).where(SysRole.del_flag == '0', SysRole.role_id == role_id).distinct()))
.scalars()
.first()
)
return query_role_info
@classmethod
async def get_role_select_option_dao(cls, db: AsyncSession) -> Sequence[SysRole]:
"""
获取编辑页面对应的在用角色列表信息
:param db: orm对象
:return: 角色列表信息
"""
role_info = (
(
await db.execute(
select(SysRole).where(SysRole.role_id != 1, SysRole.status == '0', SysRole.del_flag == '0')
)
)
.scalars()
.all()
)
return role_info
@classmethod
async def get_role_list(
cls, db: AsyncSession, query_object: RolePageQueryModel, data_scope_sql: ColumnElement, is_page: bool = False
) -> PageModel | list[dict[str, Any]]:
"""
根据查询参数获取角色列表信息
:param db: orm对象
:param query_object: 查询参数对象
:param data_scope_sql: 数据权限对应的查询sql语句
:param is_page: 是否开启分页
:return: 角色列表信息对象
"""
query = (
select(SysRole)
.join(SysUserRole, SysUserRole.role_id == SysRole.role_id, isouter=True)
.join(SysUser, SysUser.user_id == SysUserRole.user_id, isouter=True)
.join(SysDept, SysDept.dept_id == SysUser.dept_id, isouter=True)
.where(
SysRole.del_flag == '0',
SysRole.role_id == query_object.role_id if query_object.role_id is not None else True,
SysRole.role_name.like(f'%{query_object.role_name}%') if query_object.role_name else True,
SysRole.role_key.like(f'%{query_object.role_key}%') if query_object.role_key else True,
SysRole.status == query_object.status if query_object.status else True,
SysRole.create_time.between(
datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)),
datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)),
)
if query_object.begin_time and query_object.end_time
else True,
data_scope_sql,
)
.order_by(SysRole.role_sort)
.distinct()
)
role_list: PageModel | list[dict[str, Any]] = await PageUtil.paginate(
db, query, query_object.page_num, query_object.page_size, is_page
)
return role_list
@classmethod
async def add_role_dao(cls, db: AsyncSession, role: RoleModel) -> SysRole:
"""
新增角色数据库操作
:param db: orm对象
:param role: 角色对象
:return:
"""
db_role = SysRole(**role.model_dump(exclude={'admin'}))
db.add(db_role)
await db.flush()
return db_role
@classmethod
async def edit_role_dao(cls, db: AsyncSession, role: dict) -> None:
"""
编辑角色数据库操作
:param db: orm对象
:param role: 需要更新的角色字典
:return:
"""
await db.execute(update(SysRole), [role])
@classmethod
async def delete_role_dao(cls, db: AsyncSession, role: RoleModel) -> None:
"""
删除角色数据库操作
:param db: orm对象
:param role: 角色对象
:return:
"""
await db.execute(
update(SysRole)
.where(SysRole.role_id == role.role_id)
.values(del_flag='2', update_by=role.update_by, update_time=role.update_time)
)
@classmethod
async def get_role_menu_dao(cls, db: AsyncSession, role: RoleModel) -> Sequence[SysMenu]:
"""
根据角色id获取角色菜单关联列表信息
:param db: orm对象
:param role: 角色对象
:return: 角色菜单关联列表信息
"""
role_menu_query_all = (
(
await db.execute(
select(SysMenu)
.join(SysRoleMenu, SysRoleMenu.menu_id == SysMenu.menu_id)
.where(
SysRoleMenu.role_id == role.role_id,
~SysMenu.menu_id.in_(
select(SysMenu.parent_id)
.select_from(SysMenu)
.join(
SysRoleMenu,
and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysRoleMenu.role_id == role.role_id),
)
)
if role.menu_check_strictly
else True,
)
.order_by(SysMenu.parent_id, SysMenu.order_num)
)
)
.scalars()
.all()
)
return role_menu_query_all
@classmethod
async def add_role_menu_dao(cls, db: AsyncSession, role_menu: RoleMenuModel) -> None:
"""
新增角色菜单关联信息数据库操作
:param db: orm对象
:param role_menu: 用户角色菜单关联对象
:return:
"""
db_role_menu = SysRoleMenu(**role_menu.model_dump())
db.add(db_role_menu)
@classmethod
async def delete_role_menu_dao(cls, db: AsyncSession, role_menu: RoleMenuModel) -> None:
"""
删除角色菜单关联信息数据库操作
:param db: orm对象
:param role_menu: 角色菜单关联对象
:return:
"""
await db.execute(delete(SysRoleMenu).where(SysRoleMenu.role_id.in_([role_menu.role_id])))
@classmethod
async def get_role_dept_dao(cls, db: AsyncSession, role: RoleModel) -> Sequence[SysDept]:
"""
根据角色id获取角色部门关联列表信息
:param db: orm对象
:param role: 角色对象
:return: 角色部门关联列表信息
"""
role_dept_query_all = (
(
await db.execute(
select(SysDept)
.join(SysRoleDept, SysRoleDept.dept_id == SysDept.dept_id)
.where(
SysRoleDept.role_id == role.role_id,
~SysDept.dept_id.in_(
select(SysDept.parent_id)
.select_from(SysDept)
.join(
SysRoleDept,
and_(SysRoleDept.dept_id == SysDept.dept_id, SysRoleDept.role_id == role.role_id),
)
)
if role.dept_check_strictly
else True,
)
.order_by(SysDept.parent_id, SysDept.order_num)
)
)
.scalars()
.all()
)
return role_dept_query_all
@classmethod
async def add_role_dept_dao(cls, db: AsyncSession, role_dept: RoleDeptModel) -> None:
"""
新增角色部门关联信息数据库操作
:param db: orm对象
:param role_dept: 用户角色部门关联对象
:return:
"""
db_role_dept = SysRoleDept(**role_dept.dict())
db.add(db_role_dept)
@classmethod
async def delete_role_dept_dao(cls, db: AsyncSession, role_dept: RoleDeptModel) -> None:
"""
删除角色部门关联信息数据库操作
:param db: orm对象
:param role_dept: 角色部门关联对象
:return:
"""
await db.execute(delete(SysRoleDept).where(SysRoleDept.role_id.in_([role_dept.role_id])))
@classmethod
async def count_user_role_dao(cls, db: AsyncSession, role_id: int) -> int | None:
"""
根据角色id查询角色关联用户数量
:param db: orm对象
:param role_id: 角色id
:return: 角色关联用户数量
"""
user_count = (
await db.execute(select(func.count('*')).select_from(SysUserRole).where(SysUserRole.role_id == role_id))
).scalar()
return user_count
@@ -0,0 +1,587 @@
from collections.abc import Sequence
from datetime import datetime, time
from typing import Any
from sqlalchemy import ColumnElement, and_, delete, desc, func, or_, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from common.vo import PageModel
from module_admin.entity.do.dept_do import SysDept
from module_admin.entity.do.menu_do import SysMenu
from module_admin.entity.do.post_do import SysPost
from module_admin.entity.do.role_do import SysRole, SysRoleMenu
from module_admin.entity.do.user_do import SysUser, SysUserPost, SysUserRole
from module_admin.entity.vo.user_vo import (
UserModel,
UserPageQueryModel,
UserPostModel,
UserRoleModel,
UserRolePageQueryModel,
UserRoleQueryModel,
)
from utils.page_util import PageUtil
class UserDao:
"""
用户管理模块数据库操作层
"""
@classmethod
async def get_user_by_name(cls, db: AsyncSession, user_name: str) -> SysUser | None:
"""
根据用户名获取用户信息
:param db: orm对象
:param user_name: 用户名
:return: 当前用户名的用户信息对象
"""
query_user_info = (
(
await db.execute(
select(SysUser)
.where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_name == user_name)
.order_by(desc(SysUser.create_time))
.distinct()
)
)
.scalars()
.first()
)
return query_user_info
@classmethod
async def get_user_by_info(cls, db: AsyncSession, user: UserModel) -> SysUser | None:
"""
根据用户参数获取用户信息
:param db: orm对象
:param user: 用户参数
:return: 当前用户参数的用户信息对象
"""
query_user_info = (
(
await db.execute(
select(SysUser)
.where(
SysUser.del_flag == '0',
SysUser.user_name == user.user_name if user.user_name else True,
SysUser.phonenumber == user.phonenumber if user.phonenumber else True,
SysUser.email == user.email if user.email else True,
)
.order_by(desc(SysUser.create_time))
.distinct()
)
)
.scalars()
.first()
)
return query_user_info
@classmethod
async def get_user_by_id(cls, db: AsyncSession, user_id: int) -> dict[str, Any]:
"""
根据user_id获取用户信息
:param db: orm对象
:param user_id: 用户id
:return: 当前user_id的用户信息对象
"""
query_user_basic_info = (
(
await db.execute(
select(SysUser)
.where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id)
.distinct()
)
)
.scalars()
.first()
)
query_user_dept_info = (
(
await db.execute(
select(SysDept)
.select_from(SysUser)
.where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id)
.join(
SysDept,
and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == '0', SysDept.del_flag == '0'),
)
.distinct()
)
)
.scalars()
.first()
)
query_user_role_info = (
(
await db.execute(
select(SysRole)
.select_from(SysUser)
.where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id)
.join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True)
.join(
SysRole,
and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0'),
)
.distinct()
)
)
.scalars()
.all()
)
query_user_post_info = (
(
await db.execute(
select(SysPost)
.select_from(SysUser)
.where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id)
.join(SysUserPost, SysUser.user_id == SysUserPost.user_id, isouter=True)
.join(SysPost, and_(SysUserPost.post_id == SysPost.post_id, SysPost.status == '0'))
.distinct()
)
)
.scalars()
.all()
)
role_id_list = [item.role_id for item in query_user_role_info]
if 1 in role_id_list:
query_user_menu_info = (
(await db.execute(select(SysMenu).where(SysMenu.status == '0').distinct())).scalars().all()
)
else:
query_user_menu_info = (
(
await db.execute(
select(SysMenu)
.select_from(SysUser)
.where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id)
.join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True)
.join(
SysRole,
and_(
SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0'
),
isouter=True,
)
.join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True)
.join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == '0'))
.order_by(SysMenu.order_num)
.distinct()
)
)
.scalars()
.all()
)
results = {
'user_basic_info': query_user_basic_info,
'user_dept_info': query_user_dept_info,
'user_role_info': query_user_role_info,
'user_post_info': query_user_post_info,
'user_menu_info': query_user_menu_info,
}
return results
@classmethod
async def get_user_detail_by_id(cls, db: AsyncSession, user_id: int) -> dict[str, Any]:
"""
根据user_id获取用户详细信息
:param db: orm对象
:param user_id: 用户id
:return: 当前user_id的用户信息对象
"""
query_user_basic_info = (
(await db.execute(select(SysUser).where(SysUser.del_flag == '0', SysUser.user_id == user_id).distinct()))
.scalars()
.first()
)
query_user_dept_info = (
(
await db.execute(
select(SysDept)
.select_from(SysUser)
.where(SysUser.del_flag == '0', SysUser.user_id == user_id)
.join(
SysDept,
and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == '0', SysDept.del_flag == '0'),
)
.distinct()
)
)
.scalars()
.first()
)
query_user_role_info = (
(
await db.execute(
select(SysRole)
.select_from(SysUser)
.where(SysUser.del_flag == '0', SysUser.user_id == user_id)
.join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True)
.join(
SysRole,
and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0'),
)
.distinct()
)
)
.scalars()
.all()
)
query_user_post_info = (
(
await db.execute(
select(SysPost)
.select_from(SysUser)
.where(SysUser.del_flag == '0', SysUser.user_id == user_id)
.join(SysUserPost, SysUser.user_id == SysUserPost.user_id, isouter=True)
.join(SysPost, and_(SysUserPost.post_id == SysPost.post_id, SysPost.status == '0'))
.distinct()
)
)
.scalars()
.all()
)
query_user_menu_info = (
(
await db.execute(
select(SysMenu)
.select_from(SysUser)
.where(SysUser.del_flag == '0', SysUser.user_id == user_id)
.join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True)
.join(
SysRole,
and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0'),
isouter=True,
)
.join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True)
.join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == '0'))
.distinct()
)
)
.scalars()
.all()
)
results = {
'user_basic_info': query_user_basic_info,
'user_dept_info': query_user_dept_info,
'user_role_info': query_user_role_info,
'user_post_info': query_user_post_info,
'user_menu_info': query_user_menu_info,
}
return results
@classmethod
async def get_user_list(
cls, db: AsyncSession, query_object: UserPageQueryModel, data_scope_sql: ColumnElement, is_page: bool = False
) -> PageModel | list[list[dict[str, Any]]]:
"""
根据查询参数获取用户列表信息
:param db: orm对象
:param query_object: 查询参数对象
:param data_scope_sql: 数据权限对应的查询sql语句
:param is_page: 是否开启分页
:return: 用户列表信息对象
"""
query = (
select(SysUser, SysDept)
.where(
SysUser.del_flag == '0',
or_(
SysUser.dept_id == query_object.dept_id,
SysUser.dept_id.in_(
select(SysDept.dept_id).where(func.find_in_set(query_object.dept_id, SysDept.ancestors))
),
)
if query_object.dept_id
else True,
SysUser.user_id == query_object.user_id if query_object.user_id is not None else True,
SysUser.user_name.like(f'%{query_object.user_name}%') if query_object.user_name else True,
SysUser.nick_name.like(f'%{query_object.nick_name}%') if query_object.nick_name else True,
SysUser.email.like(f'%{query_object.email}%') if query_object.email else True,
SysUser.phonenumber.like(f'%{query_object.phonenumber}%') if query_object.phonenumber else True,
SysUser.status == query_object.status if query_object.status else True,
SysUser.sex == query_object.sex if query_object.sex else True,
SysUser.create_time.between(
datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)),
datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)),
)
if query_object.begin_time and query_object.end_time
else True,
data_scope_sql,
)
.join(
SysDept,
and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == '0', SysDept.del_flag == '0'),
isouter=True,
)
.order_by(SysUser.user_id)
.distinct()
)
user_list: PageModel | list[list[dict[str, Any]]] = await PageUtil.paginate(
db, query, query_object.page_num, query_object.page_size, is_page
)
return user_list
@classmethod
async def add_user_dao(cls, db: AsyncSession, user: UserModel) -> SysUser:
"""
新增用户数据库操作
:param db: orm对象
:param user: 用户对象
:return: 新增校验结果
"""
db_user = SysUser(**user.model_dump(exclude={'admin'}))
db.add(db_user)
await db.flush()
return db_user
@classmethod
async def edit_user_dao(cls, db: AsyncSession, user: dict) -> None:
"""
编辑用户数据库操作
:param db: orm对象
:param user: 需要更新的用户字典
:return: 编辑校验结果
"""
await db.execute(update(SysUser), [user])
@classmethod
async def delete_user_dao(cls, db: AsyncSession, user: UserModel) -> None:
"""
删除用户数据库操作
:param db: orm对象
:param user: 用户对象
:return:
"""
await db.execute(
update(SysUser)
.where(SysUser.user_id == user.user_id)
.values(del_flag='2', update_by=user.update_by, update_time=user.update_time)
)
@classmethod
async def get_user_role_allocated_list_by_user_id(
cls, db: AsyncSession, query_object: UserRoleQueryModel
) -> Sequence[SysRole]:
"""
根据用户id获取用户已分配的角色列表信息数据库操作
:param db: orm对象
:param query_object: 用户角色查询对象
:return: 用户已分配的角色列表信息
"""
allocated_role_list = (
(
await db.execute(
select(SysRole)
.where(
SysRole.del_flag == '0',
SysRole.role_id != 1,
SysRole.role_name == query_object.role_name if query_object.role_name else True,
SysRole.role_key == query_object.role_key if query_object.role_key else True,
SysRole.role_id.in_(
select(SysUserRole.role_id).where(SysUserRole.user_id == query_object.user_id)
),
)
.distinct()
)
)
.scalars()
.all()
)
return allocated_role_list
@classmethod
async def get_user_role_allocated_list_by_role_id(
cls,
db: AsyncSession,
query_object: UserRolePageQueryModel,
data_scope_sql: ColumnElement,
is_page: bool = False,
) -> PageModel | list[dict[str, Any]]:
"""
根据角色id获取已分配的用户列表信息
:param db: orm对象
:param query_object: 用户角色查询对象
:param data_scope_sql: 数据权限对应的查询sql语句
:param is_page: 是否开启分页
:return: 角色已分配的用户列表信息
"""
query = (
select(SysUser)
.join(SysDept, SysDept.dept_id == SysUser.dept_id, isouter=True)
.join(SysUserRole, SysUserRole.user_id == SysUser.user_id, isouter=True)
.join(SysRole, SysRole.role_id == SysUserRole.role_id, isouter=True)
.where(
SysUser.del_flag == '0',
SysUser.user_name == query_object.user_name if query_object.user_name else True,
SysUser.phonenumber == query_object.phonenumber if query_object.phonenumber else True,
SysRole.role_id == query_object.role_id,
data_scope_sql,
)
.distinct()
)
allocated_user_list: PageModel | list[dict[str, Any]] = await PageUtil.paginate(
db, query, query_object.page_num, query_object.page_size, is_page
)
return allocated_user_list
@classmethod
async def get_user_role_unallocated_list_by_role_id(
cls,
db: AsyncSession,
query_object: UserRolePageQueryModel,
data_scope_sql: ColumnElement,
is_page: bool = False,
) -> PageModel | list[dict[str, Any]]:
"""
根据角色id获取未分配的用户列表信息
:param db: orm对象
:param query_object: 用户角色查询对象
:param data_scope_sql: 数据权限对应的查询sql语句
:param is_page: 是否开启分页
:return: 角色未分配的用户列表信息
"""
query = (
select(SysUser)
.join(SysDept, SysDept.dept_id == SysUser.dept_id, isouter=True)
.join(SysUserRole, SysUserRole.user_id == SysUser.user_id, isouter=True)
.join(SysRole, SysRole.role_id == SysUserRole.role_id, isouter=True)
.where(
SysUser.del_flag == '0',
SysUser.user_name == query_object.user_name if query_object.user_name else True,
SysUser.phonenumber == query_object.phonenumber if query_object.phonenumber else True,
or_(SysRole.role_id != query_object.role_id, SysRole.role_id.is_(None)),
~SysUser.user_id.in_(
select(SysUser.user_id)
.select_from(SysUser)
.join(
SysUserRole,
and_(SysUserRole.user_id == SysUser.user_id, SysUserRole.role_id == query_object.role_id),
)
),
data_scope_sql,
)
.distinct()
)
unallocated_user_list: PageModel | list[dict[str, Any]] = await PageUtil.paginate(
db, query, query_object.page_num, query_object.page_size, is_page
)
return unallocated_user_list
@classmethod
async def add_user_role_dao(cls, db: AsyncSession, user_role: UserRoleModel) -> None:
"""
新增用户角色关联信息数据库操作
:param db: orm对象
:param user_role: 用户角色关联对象
:return:
"""
db_user_role = SysUserRole(**user_role.model_dump())
db.add(db_user_role)
@classmethod
async def delete_user_role_dao(cls, db: AsyncSession, user_role: UserRoleModel) -> None:
"""
删除用户角色关联信息数据库操作
:param db: orm对象
:param user_role: 用户角色关联对象
:return:
"""
await db.execute(delete(SysUserRole).where(SysUserRole.user_id.in_([user_role.user_id])))
@classmethod
async def delete_user_role_by_user_and_role_dao(cls, db: AsyncSession, user_role: UserRoleModel) -> None:
"""
根据用户id及角色id删除用户角色关联信息数据库操作
:param db: orm对象
:param user_role: 用户角色关联对象
:return:
"""
await db.execute(
delete(SysUserRole).where(
SysUserRole.user_id == user_role.user_id if user_role.user_id else True,
SysUserRole.role_id == user_role.role_id if user_role.role_id else True,
)
)
@classmethod
async def get_user_role_detail(cls, db: AsyncSession, user_role: UserRoleModel) -> SysUserRole | None:
"""
根据用户角色关联获取用户角色关联详细信息
:param db: orm对象
:param user_role: 用户角色关联对象
:return: 用户角色关联信息
"""
user_role_info = (
(
await db.execute(
select(SysUserRole)
.where(SysUserRole.user_id == user_role.user_id, SysUserRole.role_id == user_role.role_id)
.distinct()
)
)
.scalars()
.first()
)
return user_role_info
@classmethod
async def add_user_post_dao(cls, db: AsyncSession, user_post: UserPostModel) -> None:
"""
新增用户岗位关联信息数据库操作
:param db: orm对象
:param user_post: 用户岗位关联对象
:return:
"""
db_user_post = SysUserPost(**user_post.model_dump())
db.add(db_user_post)
@classmethod
async def delete_user_post_dao(cls, db: AsyncSession, user_post: UserPostModel) -> None:
"""
删除用户岗位关联信息数据库操作
:param db: orm对象
:param user_post: 用户岗位关联对象
:return:
"""
await db.execute(delete(SysUserPost).where(SysUserPost.user_id.in_([user_post.user_id])))
@classmethod
async def get_user_dept_info(cls, db: AsyncSession, dept_id: int) -> SysDept | None:
dept_basic_info = (
(
await db.execute(
select(SysDept).where(SysDept.dept_id == dept_id, SysDept.status == '0', SysDept.del_flag == '0')
)
)
.scalars()
.first()
)
return dept_basic_info
@@ -0,0 +1,32 @@
from datetime import datetime
from sqlalchemy import CHAR, Column, DateTime, Integer, String
from config.database import Base
from config.env import DataBaseConfig
from utils.common_util import SqlalchemyUtil
class SysConfig(Base):
"""
参数配置表
"""
__tablename__ = 'sys_config'
__table_args__ = {'comment': '参数配置表'}
config_id = Column(Integer, primary_key=True, nullable=False, autoincrement=True, comment='参数主键')
config_name = Column(String(100), nullable=True, server_default="''", comment='参数名称')
config_key = Column(String(100), nullable=True, server_default="''", comment='参数键名')
config_value = Column(String(500), nullable=True, server_default="''", comment='参数键值')
config_type = Column(CHAR(1), nullable=True, server_default='N', comment='系统内置(Y是 N否)')
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
remark = Column(
String(500),
nullable=True,
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
comment='备注',
)
@@ -0,0 +1,46 @@
from datetime import datetime
from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, String
from config.database import Base
from config.env import DataBaseConfig
from utils.common_util import SqlalchemyUtil
class SysDept(Base):
"""
部门表
"""
__tablename__ = 'sys_dept'
__table_args__ = {'comment': '部门表'}
dept_id = Column(BigInteger, primary_key=True, autoincrement=True, comment='部门id')
parent_id = Column(BigInteger, server_default='0', comment='父部门id')
ancestors = Column(String(50), nullable=True, server_default="''", comment='祖级列表')
dept_name = Column(String(30), nullable=True, server_default="''", comment='部门名称')
order_num = Column(Integer, server_default='0', comment='显示顺序')
leader = Column(
String(20),
nullable=True,
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
comment='负责人',
)
phone = Column(
String(11),
nullable=True,
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
comment='联系电话',
)
email = Column(
String(50),
nullable=True,
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
comment='邮箱',
)
status = Column(CHAR(1), nullable=True, server_default='0', comment='部门状态(0正常 1停用)')
del_flag = Column(CHAR(1), nullable=True, server_default='0', comment='删除标志(0代表存在 2代表删除)')
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
@@ -0,0 +1,70 @@
from datetime import datetime
from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, String
from config.database import Base
from config.env import DataBaseConfig
from utils.common_util import SqlalchemyUtil
class SysDictType(Base):
"""
字典类型表
"""
__tablename__ = 'sys_dict_type'
__table_args__ = {'comment': '字典类型表'}
dict_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='字典主键')
dict_name = Column(String(100), nullable=True, server_default="''", comment='字典名称')
dict_type = Column(String(100), unique=True, nullable=True, server_default="''", comment='字典类型')
status = Column(CHAR(1), nullable=True, server_default='0', comment='状态(0正常 1停用)')
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
remark = Column(
String(500),
nullable=True,
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
comment='备注',
)
class SysDictData(Base):
"""
字典数据表
"""
__tablename__ = 'sys_dict_data'
__table_args__ = {'comment': '字典数据表'}
dict_code = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='字典编码')
dict_sort = Column(Integer, nullable=True, server_default='0', comment='字典排序')
dict_label = Column(String(100), nullable=True, server_default="''", comment='字典标签')
dict_value = Column(String(100), nullable=True, server_default="''", comment='字典键值')
dict_type = Column(String(100), nullable=True, server_default="''", comment='字典类型')
css_class = Column(
String(100),
nullable=True,
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
comment='样式属性(其他样式扩展)',
)
list_class = Column(
String(100),
nullable=True,
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
comment='表格回显样式',
)
is_default = Column(CHAR(1), nullable=True, server_default='N', comment='是否默认(Y是 N否)')
status = Column(CHAR(1), nullable=True, server_default='0', comment='状态(0正常 1停用)')
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
remark = Column(
String(500),
nullable=True,
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
comment='备注',
)
@@ -0,0 +1,58 @@
from datetime import datetime
from sqlalchemy import CHAR, BigInteger, Column, DateTime, String
from config.database import Base
class SysJob(Base):
"""
定时任务调度表
"""
__tablename__ = 'sys_job'
__table_args__ = {'comment': '定时任务调度表'}
job_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='任务ID')
job_name = Column(String(64), primary_key=True, nullable=False, server_default="''", comment='任务名称')
job_group = Column(String(64), primary_key=True, nullable=False, server_default='default', comment='任务组名')
job_executor = Column(String(64), nullable=True, server_default='default', comment='任务执行器')
invoke_target = Column(String(500), nullable=False, comment='调用目标字符串')
job_args = Column(String(255), nullable=True, server_default="''", comment='位置参数')
job_kwargs = Column(String(255), nullable=True, server_default="''", comment='关键字参数')
cron_expression = Column(String(255), nullable=True, server_default="''", comment='cron执行表达式')
misfire_policy = Column(
String(20),
nullable=True,
server_default='3',
comment='计划执行错误策略(1立即执行 2执行一次 3放弃执行)',
)
concurrent = Column(CHAR(1), nullable=True, server_default='1', comment='是否并发执行(0允许 1禁止)')
status = Column(CHAR(1), nullable=True, server_default='0', comment='状态(0正常 1暂停)')
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
remark = Column(String(500), nullable=True, server_default="''", comment='备注信息')
class SysJobLog(Base):
"""
定时任务调度日志表
"""
__tablename__ = 'sys_job_log'
__table_args__ = {'comment': '定时任务调度日志表'}
job_log_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='任务日志ID')
job_name = Column(String(64), nullable=False, comment='任务名称')
job_group = Column(String(64), nullable=False, comment='任务组名')
job_executor = Column(String(64), nullable=False, comment='任务执行器')
invoke_target = Column(String(500), nullable=False, comment='调用目标字符串')
job_args = Column(String(255), nullable=True, server_default="''", comment='位置参数')
job_kwargs = Column(String(255), nullable=True, server_default="''", comment='关键字参数')
job_trigger = Column(String(255), nullable=True, server_default="''", comment='任务触发器')
job_message = Column(String(500), nullable=True, comment='日志信息')
status = Column(CHAR(1), nullable=True, server_default='0', comment='执行状态(0正常 1失败)')
exception_info = Column(String(2000), nullable=True, server_default="''", comment='异常信息')
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
@@ -0,0 +1,60 @@
from datetime import datetime
from sqlalchemy import CHAR, BigInteger, Column, DateTime, Index, Integer, String
from config.database import Base
class SysLogininfor(Base):
"""
系统访问记录
"""
__tablename__ = 'sys_logininfor'
__table_args__ = {'comment': '系统访问记录'}
info_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='访问ID')
user_name = Column(String(50), nullable=True, server_default="''", comment='用户账号')
ipaddr = Column(String(128), nullable=True, server_default="''", comment='登录IP地址')
login_location = Column(String(255), nullable=True, server_default="''", comment='登录地点')
browser = Column(String(50), nullable=True, server_default="''", comment='浏览器类型')
os = Column(String(50), nullable=True, server_default="''", comment='操作系统')
status = Column(CHAR(1), nullable=True, server_default='0', comment='登录状态(0成功 1失败)')
msg = Column(String(255), nullable=True, server_default="''", comment='提示消息')
login_time = Column(DateTime, nullable=True, default=datetime.now(), comment='访问时间')
idx_sys_logininfor_s = Index('idx_sys_logininfor_s', status)
idx_sys_logininfor_lt = Index('idx_sys_logininfor_lt', login_time)
class SysOperLog(Base):
"""
操作日志记录
"""
__tablename__ = 'sys_oper_log'
__table_args__ = {'comment': '操作日志记录'}
oper_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='日志主键')
title = Column(String(50), nullable=True, server_default="''", comment='模块标题')
business_type = Column(Integer, nullable=True, server_default='0', comment='业务类型(0其它 1新增 2修改 3删除)')
method = Column(String(100), nullable=True, server_default="''", comment='方法名称')
request_method = Column(String(10), nullable=True, server_default="''", comment='请求方式')
operator_type = Column(
Integer, nullable=True, server_default='0', comment='操作类别(0其它 1后台用户 2手机端用户)'
)
oper_name = Column(String(50), nullable=True, server_default="''", comment='操作人员')
dept_name = Column(String(50), nullable=True, server_default="''", comment='部门名称')
oper_url = Column(String(255), nullable=True, server_default="''", comment='请求URL')
oper_ip = Column(String(128), nullable=True, server_default="''", comment='主机地址')
oper_location = Column(String(255), nullable=True, server_default="''", comment='操作地点')
oper_param = Column(String(2000), nullable=True, server_default="''", comment='请求参数')
json_result = Column(String(2000), nullable=True, server_default="''", comment='返回参数')
status = Column(Integer, nullable=True, server_default='0', comment='操作状态(0正常 1异常)')
error_msg = Column(String(2000), nullable=True, server_default="''", comment='错误消息')
oper_time = Column(DateTime, nullable=True, default=datetime.now(), comment='操作时间')
cost_time = Column(BigInteger, nullable=True, server_default='0', comment='消耗时间')
idx_sys_oper_log_bt = Index('idx_sys_oper_log_bt', business_type)
idx_sys_oper_log_s = Index('idx_sys_oper_log_s', status)
idx_sys_oper_log_ot = Index('idx_sys_oper_log_ot', oper_time)
@@ -0,0 +1,52 @@
from datetime import datetime
from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, String
from config.database import Base
from config.env import DataBaseConfig
from utils.common_util import SqlalchemyUtil
class SysMenu(Base):
"""
菜单权限表
"""
__tablename__ = 'sys_menu'
__table_args__ = {'comment': '菜单权限表'}
menu_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='菜单ID')
menu_name = Column(String(50), nullable=False, comment='菜单名称')
parent_id = Column(BigInteger, nullable=True, server_default='0', comment='父菜单ID')
order_num = Column(Integer, server_default='0', comment='显示顺序')
path = Column(String(200), nullable=True, server_default="''", comment='路由地址')
component = Column(
String(255),
nullable=True,
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
comment='组件路径',
)
query = Column(
String(255),
nullable=True,
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
comment='路由参数',
)
route_name = Column(String(50), nullable=True, server_default="''", comment='路由名称')
is_frame = Column(Integer, nullable=True, server_default='1', comment='是否为外链(0是 1否)')
is_cache = Column(Integer, nullable=True, server_default='0', comment='是否缓存(0缓存 1不缓存)')
menu_type = Column(CHAR(1), nullable=True, server_default="''", comment='菜单类型(M目录 C菜单 F按钮)')
visible = Column(CHAR(1), nullable=True, server_default='0', comment='菜单状态(0显示 1隐藏)')
status = Column(CHAR(1), nullable=True, server_default='0', comment='菜单状态(0正常 1停用)')
perms = Column(
String(100),
nullable=True,
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
comment='权限标识',
)
icon = Column(String(100), nullable=True, server_default='#', comment='菜单图标')
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
remark = Column(String(500), nullable=True, server_default="''", comment='备注')
@@ -0,0 +1,38 @@
from datetime import datetime
from sqlalchemy import CHAR, Column, DateTime, Integer, LargeBinary, String
from sqlalchemy.dialects import mysql
from config.database import Base
from config.env import DataBaseConfig
from utils.common_util import SqlalchemyUtil
class SysNotice(Base):
"""
通知公告表
"""
__tablename__ = 'sys_notice'
__table_args__ = {'comment': '通知公告表'}
notice_id = Column(Integer, primary_key=True, nullable=False, autoincrement=True, comment='公告ID')
notice_title = Column(String(50), nullable=False, comment='公告标题')
notice_type = Column(CHAR(1), nullable=False, comment='公告类型(1通知 2公告)')
notice_content = Column(
mysql.LONGBLOB if DataBaseConfig.db_type == 'mysql' else LargeBinary,
nullable=True,
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type, False),
comment='公告内容',
)
status = Column(CHAR(1), nullable=True, server_default='0', comment='公告状态(0正常 1关闭)')
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
create_time = Column(DateTime, nullable=True, comment='创建时间', default=datetime.now())
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
update_time = Column(DateTime, nullable=True, comment='更新时间', default=datetime.now())
remark = Column(
String(255),
nullable=True,
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
comment='备注',
)
@@ -0,0 +1,32 @@
from datetime import datetime
from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, String
from config.database import Base
from config.env import DataBaseConfig
from utils.common_util import SqlalchemyUtil
class SysPost(Base):
"""
岗位信息表
"""
__tablename__ = 'sys_post'
__table_args__ = {'comment': '岗位信息表'}
post_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='岗位ID')
post_code = Column(String(64), nullable=False, comment='岗位编码')
post_name = Column(String(50), nullable=False, comment='岗位名称')
post_sort = Column(Integer, nullable=False, comment='显示顺序')
status = Column(CHAR(1), nullable=False, comment='状态(0正常 1停用)')
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
remark = Column(
String(500),
nullable=True,
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
comment='备注',
)
@@ -0,0 +1,76 @@
from datetime import datetime
from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, SmallInteger, String
from sqlalchemy.dialects import mysql
from config.database import Base
from config.env import DataBaseConfig
from utils.common_util import SqlalchemyUtil
class SysRole(Base):
"""
角色信息表
"""
__tablename__ = 'sys_role'
__table_args__ = {'comment': '角色信息表'}
role_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='角色ID')
role_name = Column(String(30), nullable=False, comment='角色名称')
role_key = Column(String(100), nullable=False, comment='角色权限字符串')
role_sort = Column(Integer, nullable=False, comment='显示顺序')
data_scope = Column(
CHAR(1),
nullable=True,
server_default='1',
comment='数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)',
)
menu_check_strictly = Column(
mysql.TINYINT(display_width=1) if DataBaseConfig.db_type == 'mysql' else SmallInteger,
nullable=True,
server_default='1',
comment='菜单树选择项是否关联显示',
)
dept_check_strictly = Column(
mysql.TINYINT(display_width=1) if DataBaseConfig.db_type == 'mysql' else SmallInteger,
nullable=True,
server_default='1',
comment='部门树选择项是否关联显示',
)
status = Column(CHAR(1), nullable=False, comment='角色状态(0正常 1停用)')
del_flag = Column(CHAR(1), nullable=True, server_default='0', comment='删除标志(0代表存在 2代表删除)')
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
remark = Column(
String(500),
nullable=True,
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
comment='备注',
)
class SysRoleDept(Base):
"""
角色和部门关联表
"""
__tablename__ = 'sys_role_dept'
__table_args__ = {'comment': '角色和部门关联表'}
role_id = Column(BigInteger, primary_key=True, nullable=False, comment='角色ID')
dept_id = Column(BigInteger, primary_key=True, nullable=False, comment='部门ID')
class SysRoleMenu(Base):
"""
角色和菜单关联表
"""
__tablename__ = 'sys_role_menu'
__table_args__ = {'comment': '角色和菜单关联表'}
role_id = Column(BigInteger, primary_key=True, nullable=False, comment='角色ID')
menu_id = Column(BigInteger, primary_key=True, nullable=False, comment='菜单ID')
@@ -0,0 +1,71 @@
from datetime import datetime
from sqlalchemy import CHAR, BigInteger, Column, DateTime, String
from config.database import Base
from config.env import DataBaseConfig
from utils.common_util import SqlalchemyUtil
class SysUser(Base):
"""
用户信息表
"""
__tablename__ = 'sys_user'
__table_args__ = {'comment': '用户信息表'}
user_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='用户ID')
dept_id = Column(
BigInteger,
nullable=True,
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type, False),
comment='部门ID',
)
user_name = Column(String(30), nullable=False, comment='用户账号')
nick_name = Column(String(30), nullable=False, comment='用户昵称')
user_type = Column(String(2), nullable=True, server_default='00', comment='用户类型(00系统用户)')
email = Column(String(50), nullable=True, server_default="''", comment='用户邮箱')
phonenumber = Column(String(11), nullable=True, server_default="''", comment='手机号码')
sex = Column(CHAR(1), nullable=True, server_default='0', comment='用户性别(0男 1女 2未知)')
avatar = Column(String(100), nullable=True, server_default="''", comment='头像地址')
password = Column(String(100), nullable=True, server_default="''", comment='密码')
status = Column(CHAR(1), nullable=True, server_default='0', comment='帐号状态(0正常 1停用)')
del_flag = Column(CHAR(1), nullable=True, server_default='0', comment='删除标志(0代表存在 2代表删除)')
login_ip = Column(String(128), nullable=True, server_default="''", comment='最后登录IP')
login_date = Column(DateTime, nullable=True, comment='最后登录时间')
pwd_update_date = Column(DateTime, nullable=True, comment='密码最后更新时间')
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
create_time = Column(DateTime, nullable=True, comment='创建时间', default=datetime.now())
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
update_time = Column(DateTime, nullable=True, comment='更新时间', default=datetime.now())
remark = Column(
String(500),
nullable=True,
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
comment='备注',
)
class SysUserRole(Base):
"""
用户和角色关联表
"""
__tablename__ = 'sys_user_role'
__table_args__ = {'comment': '用户和角色关联表'}
user_id = Column(BigInteger, primary_key=True, nullable=False, comment='用户ID')
role_id = Column(BigInteger, primary_key=True, nullable=False, comment='角色ID')
class SysUserPost(Base):
"""
用户与岗位关联表
"""
__tablename__ = 'sys_user_post'
__table_args__ = {'comment': '用户与岗位关联表'}
user_id = Column(BigInteger, primary_key=True, nullable=False, comment='用户ID')
post_id = Column(BigInteger, primary_key=True, nullable=False, comment='岗位ID')
@@ -0,0 +1,29 @@
from typing import Any
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
class CacheMonitorModel(BaseModel):
"""
缓存监控信息对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel)
command_stats: list | None = Field(default=[], description='命令统计')
db_size: int | None = Field(default=None, description='Key数量')
info: dict | None = Field(default={}, description='Redis信息')
class CacheInfoModel(BaseModel):
"""
缓存监控对象对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel)
cache_key: str | None = Field(default=None, description='缓存键名')
cache_name: str | None = Field(default=None, description='缓存名称')
cache_value: Any | None = Field(default=None, description='缓存内容')
remark: str | None = Field(default=None, description='备注')
@@ -0,0 +1,15 @@
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
class UploadResponseModel(BaseModel):
"""
上传响应模型
"""
model_config = ConfigDict(alias_generator=to_camel)
file_name: str | None = Field(default=None, description='新文件映射路径')
new_file_name: str | None = Field(default=None, description='新文件名称')
original_filename: str | None = Field(default=None, description='原文件名称')
url: str | None = Field(default=None, description='新文件url')
@@ -0,0 +1,73 @@
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import NotBlank, Size
class ConfigModel(BaseModel):
"""
参数配置表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
config_id: int | None = Field(default=None, description='参数主键')
config_name: str | None = Field(default=None, description='参数名称')
config_key: str | None = Field(default=None, description='参数键名')
config_value: str | None = Field(default=None, description='参数键值')
config_type: Literal['Y', 'N'] | None = Field(default=None, description='系统内置(Y是 N否)')
create_by: str | None = Field(default=None, description='创建者')
create_time: datetime | None = Field(default=None, description='创建时间')
update_by: str | None = Field(default=None, description='更新者')
update_time: datetime | None = Field(default=None, description='更新时间')
remark: str | None = Field(default=None, description='备注')
@NotBlank(field_name='config_key', message='参数名称不能为空')
@Size(field_name='config_key', min_length=0, max_length=100, message='参数名称长度不能超过100个字符')
def get_config_key(self) -> str | None:
return self.config_key
@NotBlank(field_name='config_name', message='参数键名不能为空')
@Size(field_name='config_name', min_length=0, max_length=100, message='参数键名长度不能超过100个字符')
def get_config_name(self) -> str | None:
return self.config_name
@NotBlank(field_name='config_value', message='参数键值不能为空')
@Size(field_name='config_value', min_length=0, max_length=500, message='参数键值长度不能超过500个字符')
def get_config_value(self) -> str | None:
return self.config_value
def validate_fields(self) -> None:
self.get_config_key()
self.get_config_name()
self.get_config_value()
class ConfigQueryModel(ConfigModel):
"""
参数配置管理不分页查询模型
"""
begin_time: str | None = Field(default=None, description='开始时间')
end_time: str | None = Field(default=None, description='结束时间')
class ConfigPageQueryModel(ConfigQueryModel):
"""
参数配置管理分页查询模型
"""
page_num: int = Field(default=1, description='当前页码')
page_size: int = Field(default=10, description='每页记录数')
class DeleteConfigModel(BaseModel):
"""
删除参数配置模型
"""
model_config = ConfigDict(alias_generator=to_camel)
config_ids: str = Field(description='需要删除的参数主键')
@@ -0,0 +1,87 @@
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import Network, NotBlank, Size
class DeptModel(BaseModel):
"""
部门表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
dept_id: int | None = Field(default=None, description='部门id')
parent_id: int | None = Field(default=None, description='父部门id')
ancestors: str | None = Field(default=None, description='祖级列表')
dept_name: str | None = Field(default=None, description='部门名称')
order_num: int | None = Field(default=None, description='显示顺序')
leader: str | None = Field(default=None, description='负责人')
phone: str | None = Field(default=None, description='联系电话')
email: str | None = Field(default=None, description='邮箱')
status: Literal['0', '1'] | None = Field(default=None, description='部门状态(0正常 1停用)')
del_flag: Literal['0', '2'] | None = Field(default=None, description='删除标志(0代表存在 2代表删除)')
create_by: str | None = Field(default=None, description='创建者')
create_time: datetime | None = Field(default=None, description='创建时间')
update_by: str | None = Field(default=None, description='更新者')
update_time: datetime | None = Field(default=None, description='更新时间')
@NotBlank(field_name='dept_name', message='部门名称不能为空')
@Size(field_name='dept_name', min_length=0, max_length=30, message='部门名称长度不能超过30个字符')
def get_dept_name(self) -> str | None:
return self.dept_name
@NotBlank(field_name='order_num', message='显示顺序不能为空')
def get_order_num(self) -> int | None:
return self.order_num
@Size(field_name='phone', min_length=0, max_length=11, message='联系电话长度不能超过11个字符')
def get_phone(self) -> str | None:
return self.phone
@Network(field_name='email', field_type='EmailStr', message='邮箱格式不正确')
@Size(field_name='email', min_length=0, max_length=50, message='邮箱长度不能超过50个字符')
def get_email(self) -> str | None:
return self.email
def validate_fields(self) -> None:
self.get_dept_name()
self.get_order_num()
self.get_phone()
self.get_email()
class DeptQueryModel(DeptModel):
"""
部门管理不分页查询模型
"""
begin_time: str | None = Field(default=None, description='开始时间')
end_time: str | None = Field(default=None, description='结束时间')
class DeptTreeModel(BaseModel):
"""
部门树模型
"""
model_config = ConfigDict(alias_generator=to_camel)
id: int = Field(description='部门id')
label: str = Field(description='部门名称')
parent_id: int = Field(description='父部门id')
children: list['DeptTreeModel'] | None = Field(default=None, description='子部门树')
class DeleteDeptModel(BaseModel):
"""
删除部门模型
"""
model_config = ConfigDict(alias_generator=to_camel)
dept_ids: str = Field(default=None, description='需要删除的部门id')
update_by: str | None = Field(default=None, description='更新者')
update_time: str | None = Field(default=None, description='更新时间')
@@ -0,0 +1,147 @@
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import NotBlank, Pattern, Size
class DictTypeModel(BaseModel):
"""
字典类型表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
dict_id: int | None = Field(default=None, description='字典主键')
dict_name: str | None = Field(default=None, description='字典名称')
dict_type: str | None = Field(default=None, description='字典类型')
status: Literal['0', '1'] | None = Field(default=None, description='状态(0正常 1停用)')
create_by: str | None = Field(default=None, description='创建者')
create_time: datetime | None = Field(default=None, description='创建时间')
update_by: str | None = Field(default=None, description='更新者')
update_time: datetime | None = Field(default=None, description='更新时间')
remark: str | None = Field(default=None, description='备注')
@NotBlank(field_name='dict_name', message='字典名称不能为空')
@Size(field_name='dict_name', min_length=0, max_length=100, message='字典类型名称长度不能超过100个字符')
def get_dict_name(self) -> str | None:
return self.dict_name
@NotBlank(field_name='dict_type', message='字典类型不能为空')
@Size(field_name='dict_type', min_length=0, max_length=100, message='字典类型类型长度不能超过100个字符')
@Pattern(
field_name='dict_type',
regexp='^[a-z][a-z0-9_]*$',
message='字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)',
)
def get_dict_type(self) -> str | None:
return self.dict_type
def validate_fields(self) -> None:
self.get_dict_name()
self.get_dict_type()
class DictDataModel(BaseModel):
"""
字典数据表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
dict_code: int | None = Field(default=None, description='字典编码')
dict_sort: int | None = Field(default=None, description='字典排序')
dict_label: str | None = Field(default=None, description='字典标签')
dict_value: str | None = Field(default=None, description='字典键值')
dict_type: str | None = Field(default=None, description='字典类型')
css_class: str | None = Field(default=None, description='样式属性(其他样式扩展)')
list_class: str | None = Field(default=None, description='表格回显样式')
is_default: Literal['Y', 'N'] | None = Field(default=None, description='是否默认(Y是 N否)')
status: Literal['0', '1'] | None = Field(default=None, description='状态(0正常 1停用)')
create_by: str | None = Field(default=None, description='创建者')
create_time: datetime | None = Field(default=None, description='创建时间')
update_by: str | None = Field(default=None, description='更新者')
update_time: datetime | None = Field(default=None, description='更新时间')
remark: str | None = Field(default=None, description='备注')
@NotBlank(field_name='dict_label', message='字典标签不能为空')
@Size(field_name='dict_label', min_length=0, max_length=100, message='字典标签长度不能超过100个字符')
def get_dict_label(self) -> str | None:
return self.dict_label
@NotBlank(field_name='dict_value', message='字典键值不能为空')
@Size(field_name='dict_value', min_length=0, max_length=100, message='字典键值长度不能超过100个字符')
def get_dict_value(self) -> str | None:
return self.dict_value
@NotBlank(field_name='dict_type', message='字典类型不能为空')
@Size(field_name='dict_type', min_length=0, max_length=100, message='字典类型长度不能超过100个字符')
def get_dict_type(self) -> str | None:
return self.dict_type
@Size(field_name='css_class', min_length=0, max_length=100, message='样式属性长度不能超过100个字符')
def get_css_class(self) -> str | None:
return self.css_class
def validate_fields(self) -> None:
self.get_dict_label()
self.get_dict_value()
self.get_dict_type()
self.get_css_class()
class DictTypeQueryModel(DictTypeModel):
"""
字典类型管理不分页查询模型
"""
begin_time: str | None = Field(default=None, description='开始时间')
end_time: str | None = Field(default=None, description='结束时间')
class DictTypePageQueryModel(DictTypeQueryModel):
"""
字典类型管理分页查询模型
"""
page_num: int = Field(default=1, description='当前页码')
page_size: int = Field(default=10, description='每页记录数')
class DeleteDictTypeModel(BaseModel):
"""
删除字典类型模型
"""
model_config = ConfigDict(alias_generator=to_camel)
dict_ids: str = Field(description='需要删除的字典主键')
class DictDataQueryModel(DictDataModel):
"""
字典数据管理不分页查询模型
"""
begin_time: str | None = Field(default=None, description='开始时间')
end_time: str | None = Field(default=None, description='结束时间')
class DictDataPageQueryModel(DictDataQueryModel):
"""
字典数据管理分页查询模型
"""
page_num: int = Field(default=1, description='当前页码')
page_size: int = Field(default=10, description='每页记录数')
class DeleteDictDataModel(BaseModel):
"""
删除字典数据模型
"""
model_config = ConfigDict(alias_generator=to_camel)
dict_codes: str = Field(description='需要删除的字典编码')
@@ -0,0 +1,132 @@
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import NotBlank, Size
class JobModel(BaseModel):
"""
定时任务调度表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
job_id: int | None = Field(default=None, description='任务ID')
job_name: str | None = Field(default=None, description='任务名称')
job_group: str | None = Field(default=None, description='任务组名')
job_executor: str | None = Field(default=None, description='任务执行器')
invoke_target: str | None = Field(default=None, description='调用目标字符串')
job_args: str | None = Field(default=None, description='位置参数')
job_kwargs: str | None = Field(default=None, description='关键字参数')
cron_expression: str | None = Field(default=None, description='cron执行表达式')
misfire_policy: Literal['1', '2', '3'] | None = Field(
default=None, description='计划执行错误策略(1立即执行 2执行一次 3放弃执行)'
)
concurrent: Literal['0', '1'] | None = Field(default=None, description='是否并发执行(0允许 1禁止)')
status: Literal['0', '1'] | None = Field(default=None, description='状态(0正常 1暂停)')
create_by: str | None = Field(default=None, description='创建者')
create_time: datetime | None = Field(default=None, description='创建时间')
update_by: str | None = Field(default=None, description='更新者')
update_time: datetime | None = Field(default=None, description='更新时间')
remark: str | None = Field(default=None, description='备注信息')
@NotBlank(field_name='invoke_target', message='调用目标字符串不能为空')
@Size(field_name='invoke_target', min_length=0, max_length=500, message='调用目标字符串长度不能超过500个字符')
def get_invoke_target(self) -> str | None:
return self.invoke_target
@NotBlank(field_name='cron_expression', message='Cron执行表达式不能为空')
@Size(field_name='cron_expression', min_length=0, max_length=255, message='Cron执行表达式不能超过255个字符')
def get_cron_expression(self) -> str | None:
return self.cron_expression
def validate_fields(self) -> None:
self.get_invoke_target()
self.get_cron_expression()
class JobLogModel(BaseModel):
"""
定时任务调度日志表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
job_log_id: int | None = Field(default=None, description='任务日志ID')
job_name: str | None = Field(default=None, description='任务名称')
job_group: str | None = Field(default=None, description='任务组名')
job_executor: str | None = Field(default=None, description='任务执行器')
invoke_target: str | None = Field(default=None, description='调用目标字符串')
job_args: str | None = Field(default=None, description='位置参数')
job_kwargs: str | None = Field(default=None, description='关键字参数')
job_trigger: str | None = Field(default=None, description='任务触发器')
job_message: str | None = Field(default=None, description='日志信息')
status: Literal['0', '1'] | None = Field(default=None, description='执行状态(0正常 1失败)')
exception_info: str | None = Field(default=None, description='异常信息')
create_time: datetime | None = Field(default=None, description='创建时间')
class JobQueryModel(JobModel):
"""
定时任务管理不分页查询模型
"""
begin_time: str | None = Field(default=None, description='开始时间')
end_time: str | None = Field(default=None, description='结束时间')
class JobPageQueryModel(JobQueryModel):
"""
定时任务管理分页查询模型
"""
page_num: int = Field(default=1, description='当前页码')
page_size: int = Field(default=10, description='每页记录数')
class EditJobModel(JobModel):
"""
编辑定时任务模型
"""
type: str | None = Field(default=None, description='操作类型')
class DeleteJobModel(BaseModel):
"""
删除定时任务模型
"""
model_config = ConfigDict(alias_generator=to_camel)
job_ids: str = Field(description='需要删除的定时任务ID')
class JobLogQueryModel(JobLogModel):
"""
定时任务日志不分页查询模型
"""
begin_time: str | None = Field(default=None, description='开始时间')
end_time: str | None = Field(default=None, description='结束时间')
class JobLogPageQueryModel(JobLogQueryModel):
"""
定时任务日志管理分页查询模型
"""
page_num: int = Field(default=1, description='当前页码')
page_size: int = Field(default=10, description='每页记录数')
class DeleteJobLogModel(BaseModel):
"""
删除定时任务日志模型
"""
model_config = ConfigDict(alias_generator=to_camel)
job_log_ids: str = Field(description='需要删除的定时任务日志ID')
@@ -0,0 +1,127 @@
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
class OperLogModel(BaseModel):
"""
操作日志表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
oper_id: int | None = Field(default=None, description='日志主键')
title: str | None = Field(default=None, description='模块标题')
business_type: Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] | None = (
Field(
default=None, description='业务类型(0其它 1新增 2修改 3删除 4授权 5导出 6导入 7强退 8生成代码 9清空数据)'
)
)
method: str | None = Field(default=None, description='方法名称')
request_method: str | None = Field(default=None, description='请求方式')
operator_type: Literal[0, 1, 2] | None = Field(default=None, description='操作类别(0其它 1后台用户 2手机端用户)')
oper_name: str | None = Field(default=None, description='操作人员')
dept_name: str | None = Field(default=None, description='部门名称')
oper_url: str | None = Field(default=None, description='请求URL')
oper_ip: str | None = Field(default=None, description='主机地址')
oper_location: str | None = Field(default=None, description='操作地点')
oper_param: str | None = Field(default=None, description='请求参数')
json_result: str | None = Field(default=None, description='返回参数')
status: Literal[0, 1, '0', '1'] | None = Field(default=None, description='操作状态(0正常 1异常)')
error_msg: str | None = Field(default=None, description='错误消息')
oper_time: datetime | None = Field(default=None, description='操作时间')
cost_time: int | None = Field(default=None, description='消耗时间')
class LogininforModel(BaseModel):
"""
登录日志表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
info_id: int | None = Field(default=None, description='访问ID')
user_name: str | None = Field(default=None, description='用户账号')
ipaddr: str | None = Field(default=None, description='登录IP地址')
login_location: str | None = Field(default=None, description='登录地点')
browser: str | None = Field(default=None, description='浏览器类型')
os: str | None = Field(default=None, description='操作系统')
status: Literal['0', '1'] | None = Field(default=None, description='登录状态(0成功 1失败)')
msg: str | None = Field(default=None, description='提示消息')
login_time: datetime | None = Field(default=None, description='访问时间')
class OperLogQueryModel(OperLogModel):
"""
操作日志管理不分页查询模型
"""
order_by_column: str | None = Field(default=None, description='排序的字段名称')
is_asc: Literal['ascending', 'descending'] | None = Field(
default=None, description='排序方式(ascending升序 descending降序)'
)
begin_time: str | None = Field(default=None, description='开始时间')
end_time: str | None = Field(default=None, description='结束时间')
class OperLogPageQueryModel(OperLogQueryModel):
"""
操作日志管理分页查询模型
"""
page_num: int = Field(default=1, description='当前页码')
page_size: int = Field(default=10, description='每页记录数')
class DeleteOperLogModel(BaseModel):
"""
删除操作日志模型
"""
model_config = ConfigDict(alias_generator=to_camel)
oper_ids: str = Field(description='需要删除的日志主键')
class LoginLogQueryModel(LogininforModel):
"""
登录日志管理不分页查询模型
"""
order_by_column: str | None = Field(default=None, description='排序的字段名称')
is_asc: Literal['ascending', 'descending'] | None = Field(
default=None, description='排序方式(ascending升序 descending降序)'
)
begin_time: str | None = Field(default=None, description='开始时间')
end_time: str | None = Field(default=None, description='结束时间')
class LoginLogPageQueryModel(LoginLogQueryModel):
"""
登录日志管理分页查询模型
"""
page_num: int = Field(default=1, description='当前页码')
page_size: int = Field(default=10, description='每页记录数')
class DeleteLoginLogModel(BaseModel):
"""
删除登录日志模型
"""
model_config = ConfigDict(alias_generator=to_camel)
info_ids: str = Field(description='需要删除的访问ID')
class UnlockUser(BaseModel):
"""
解锁用户模型
"""
model_config = ConfigDict(alias_generator=to_camel)
user_name: str = Field(description='用户名称')
@@ -0,0 +1,93 @@
import re
from pydantic import BaseModel, ConfigDict, Field, model_validator
from pydantic.alias_generators import to_camel
from exceptions.exception import ModelValidatorException
from module_admin.entity.vo.menu_vo import MenuModel
class UserLogin(BaseModel):
model_config = ConfigDict(alias_generator=to_camel)
user_name: str = Field(description='用户名称')
password: str = Field(description='用户密码')
code: str | None = Field(default=None, description='验证码')
uuid: str | None = Field(default=None, description='会话编号')
login_info: dict | None = Field(default=None, description='登录信息,前端无需传递')
captcha_enabled: bool | None = Field(default=None, description='是否启用验证码,前端无需传递')
class UserRegister(BaseModel):
model_config = ConfigDict(alias_generator=to_camel)
username: str = Field(description='用户名称')
password: str = Field(description='用户密码')
confirm_password: str = Field(description='用户二次确认密码')
code: str | None = Field(default=None, description='验证码')
uuid: str | None = Field(default=None, description='会话编号')
@model_validator(mode='after')
def check_password(self) -> 'UserRegister':
pattern = r"""^[^<>"'|\\]+$"""
if self.password is None or re.match(pattern, self.password):
return self
raise ModelValidatorException(message='密码不能包含非法字符:< > " \' \\ |')
class Token(BaseModel):
access_token: str = Field(description='token信息')
token_type: str = Field(description='token类型')
class LoginToken(BaseModel):
model_config = ConfigDict(alias_generator=to_camel)
token: str = Field(description='token信息')
class CaptchaCode(BaseModel):
model_config = ConfigDict(alias_generator=to_camel)
captcha_enabled: bool = Field(description='是否启用验证码')
register_enabled: bool = Field(description='是否启用注册')
img: str = Field(description='验证码图片')
uuid: str = Field(description='会话编号')
class SmsCode(BaseModel):
is_success: bool | None = Field(default=None, description='操作是否成功')
sms_code: str = Field(description='短信验证码')
session_id: str = Field(description='会话编号')
message: str | None = Field(default=None, description='响应信息')
class MenuTreeModel(MenuModel):
children: list['MenuTreeModel'] | None | None = Field(default=None, description='子菜单')
class MetaModel(BaseModel):
model_config = ConfigDict(alias_generator=to_camel)
title: str | None = Field(default=None, description='设置路由在侧边栏和面包屑中展示的名字')
icon: str | None = Field(default=None, description='设置路由的图标')
no_cache: bool | None = Field(default=None, description='设置为true,则不会被 <keep-alive>缓存')
link: str | None = Field(default=None, description='内链地址(http(s)://开头)')
class RouterModel(BaseModel):
model_config = ConfigDict(alias_generator=to_camel)
name: str | None = Field(default=None, description='路由名称')
path: str | None = Field(default=None, description='路由地址')
hidden: bool | None = Field(default=None, description='是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现')
redirect: str | None = Field(
default=None, description='重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击'
)
component: str | None = Field(default=None, description='组件地址')
query: str | None = Field(default=None, description='路由参数:如 {"id": 1, "name": "ry"}')
always_show: bool | None = Field(
default=None, description='当你一个路由下面的children声明的路由大于1个时,自动会变成嵌套的模式--如组件页面'
)
meta: MetaModel | None = Field(default=None, description='其他元素')
children: list['RouterModel'] | None | None = Field(default=None, description='子路由')
@@ -0,0 +1,100 @@
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import NotBlank, Size
class MenuModel(BaseModel):
"""
菜单表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
menu_id: int | None = Field(default=None, description='菜单ID')
menu_name: str | None = Field(default=None, description='菜单名称')
parent_id: int | None = Field(default=None, description='父菜单ID')
order_num: int | None = Field(default=None, description='显示顺序')
path: str | None = Field(default=None, description='路由地址')
component: str | None = Field(default=None, description='组件路径')
query: str | None = Field(default=None, description='路由参数')
route_name: str | None = Field(default=None, description='路由名称')
is_frame: Literal[0, 1] | None = Field(default=None, description='是否为外链(0是 1否)')
is_cache: Literal[0, 1] | None = Field(default=None, description='是否缓存(0缓存 1不缓存)')
menu_type: Literal['M', 'C', 'F'] | None = Field(default=None, description='菜单类型(M目录 C菜单 F按钮)')
visible: Literal['0', '1'] | None = Field(default=None, description='菜单状态(0显示 1隐藏)')
status: Literal['0', '1'] | None = Field(default=None, description='菜单状态(0正常 1停用)')
perms: str | None = Field(default=None, description='权限标识')
icon: str | None = Field(default=None, description='菜单图标')
create_by: str | None = Field(default=None, description='创建者')
create_time: datetime | None = Field(default=None, description='创建时间')
update_by: str | None = Field(default=None, description='更新者')
update_time: datetime | None = Field(default=None, description='更新时间')
remark: str | None = Field(default=None, description='备注')
@NotBlank(field_name='menu_name', message='菜单名称不能为空')
@Size(field_name='menu_name', min_length=0, max_length=50, message='菜单名称长度不能超过50个字符')
def get_menu_name(self) -> str | None:
return self.menu_name
@NotBlank(field_name='order_num', message='显示顺序不能为空')
def get_order_num(self) -> int | None:
return self.order_num
@Size(field_name='path', min_length=0, max_length=200, message='路由地址长度不能超过200个字符')
def get_path(self) -> str | None:
return self.path
@Size(field_name='component', min_length=0, max_length=255, message='组件路径长度不能超过255个字符')
def get_component(self) -> str | None:
return self.component
@NotBlank(field_name='menu_type', message='菜单类型不能为空')
def get_menu_type(self) -> Literal['M', 'C', 'F'] | None:
return self.menu_type
@Size(field_name='perms', min_length=0, max_length=100, message='权限标识长度不能超过100个字符')
def get_perms(self) -> str | None:
return self.perms
def validate_fields(self) -> None:
self.get_menu_name()
self.get_order_num()
self.get_path()
self.get_component()
self.get_menu_type()
self.get_perms()
class MenuQueryModel(MenuModel):
"""
菜单管理不分页查询模型
"""
begin_time: str | None = Field(default=None, description='开始时间')
end_time: str | None = Field(default=None, description='结束时间')
class MenuTreeModel(BaseModel):
"""
菜单树模型
"""
model_config = ConfigDict(alias_generator=to_camel)
id: int = Field(description='菜单id')
label: str = Field(description='菜单名称')
parent_id: int = Field(description='父菜单id')
children: list['MenuTreeModel'] | None = Field(default=None, description='子菜单树')
class DeleteMenuModel(BaseModel):
"""
删除菜单模型
"""
model_config = ConfigDict(alias_generator=to_camel)
menu_ids: str = Field(description='需要删除的菜单ID')
@@ -0,0 +1,62 @@
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import NotBlank, Size, Xss
class NoticeModel(BaseModel):
"""
通知公告表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
notice_id: int | None = Field(default=None, description='公告ID')
notice_title: str | None = Field(default=None, description='公告标题')
notice_type: Literal['1', '2'] | None = Field(default=None, description='公告类型(1通知 2公告)')
notice_content: bytes | None = Field(default=None, description='公告内容')
status: Literal['0', '1'] | None = Field(default=None, description='公告状态(0正常 1关闭)')
create_by: str | None = Field(default=None, description='创建者')
create_time: datetime | None = Field(default=None, description='创建时间')
update_by: str | None = Field(default=None, description='更新者')
update_time: datetime | None = Field(default=None, description='更新时间')
remark: str | None = Field(default=None, description='备注')
@Xss(field_name='notice_title', message='公告标题不能包含脚本字符')
@NotBlank(field_name='notice_title', message='公告标题不能为空')
@Size(field_name='notice_title', min_length=0, max_length=50, message='公告标题不能超过50个字符')
def get_notice_title(self) -> str | None:
return self.notice_title
def validate_fields(self) -> None:
self.get_notice_title()
class NoticeQueryModel(NoticeModel):
"""
通知公告管理不分页查询模型
"""
begin_time: str | None = Field(default=None, description='开始时间')
end_time: str | None = Field(default=None, description='结束时间')
class NoticePageQueryModel(NoticeQueryModel):
"""
通知公告管理分页查询模型
"""
page_num: int = Field(default=1, description='当前页码')
page_size: int = Field(default=10, description='每页记录数')
class DeleteNoticeModel(BaseModel):
"""
删除通知公告模型
"""
model_config = ConfigDict(alias_generator=to_camel)
notice_ids: str = Field(description='需要删除的公告ID')
@@ -0,0 +1,51 @@
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
class OnlineModel(BaseModel):
"""
在线用户对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel)
token_id: str | None = Field(default=None, description='会话编号')
user_name: str | None = Field(default=None, description='登录名称')
dept_name: str | None = Field(default=None, description='所属部门')
ipaddr: str | None = Field(default=None, description='主机')
login_location: str | None = Field(default=None, description='登录地点')
browser: str | None = Field(default=None, description='浏览器类型')
os: str | None = Field(default=None, description='操作系统')
login_time: datetime | None = Field(default=None, description='登录时间')
class OnlineQueryModel(OnlineModel):
"""
岗位管理不分页查询模型
"""
begin_time: str | None = Field(default=None, description='开始时间')
end_time: str | None = Field(default=None, description='结束时间')
class OnlinePageResponseModel(BaseModel):
"""
在线用户分页响应模型
"""
model_config = ConfigDict(alias_generator=to_camel)
rows: list[OnlineModel] = Field(description='在线用户记录列表')
total: int = Field(description='总记录数')
class DeleteOnlineModel(BaseModel):
"""
强退在线用户模型
"""
model_config = ConfigDict(alias_generator=to_camel)
token_ids: str = Field(description='需要强退的会话编号')
@@ -0,0 +1,72 @@
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import NotBlank, Size
class PostModel(BaseModel):
"""
岗位信息表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
post_id: int | None = Field(default=None, description='岗位ID')
post_code: str | None = Field(default=None, description='岗位编码')
post_name: str | None = Field(default=None, description='岗位名称')
post_sort: int | None = Field(default=None, description='显示顺序')
status: Literal['0', '1'] | None = Field(default=None, description='状态(0正常 1停用)')
create_by: str | None = Field(default=None, description='创建者')
create_time: datetime | None = Field(default=None, description='创建时间')
update_by: str | None = Field(default=None, description='更新者')
update_time: datetime | None = Field(default=None, description='更新时间')
remark: str | None = Field(default=None, description='备注')
@NotBlank(field_name='post_code', message='岗位编码不能为空')
@Size(field_name='post_code', min_length=0, max_length=64, message='岗位编码长度不能超过64个字符')
def get_post_code(self) -> str | None:
return self.post_code
@NotBlank(field_name='post_name', message='岗位名称不能为空')
@Size(field_name='post_name', min_length=0, max_length=50, message='岗位名称长度不能超过50个字符')
def get_post_name(self) -> str | None:
return self.post_name
@NotBlank(field_name='post_sort', message='显示顺序不能为空')
def get_post_sort(self) -> int | None:
return self.post_sort
def validate_fields(self) -> None:
self.get_post_code()
self.get_post_name()
self.get_post_sort()
class PostQueryModel(PostModel):
"""
岗位管理不分页查询模型
"""
begin_time: str | None = Field(default=None, description='开始时间')
end_time: str | None = Field(default=None, description='结束时间')
class PostPageQueryModel(PostQueryModel):
"""
岗位管理分页查询模型
"""
page_num: int = Field(default=1, description='当前页码')
page_size: int = Field(default=10, description='每页记录数')
class DeletePostModel(BaseModel):
"""
删除岗位模型
"""
model_config = ConfigDict(alias_generator=to_camel)
post_ids: str = Field(description='需要删除的岗位ID')
@@ -0,0 +1,157 @@
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import NotBlank, Size
class RoleModel(BaseModel):
"""
角色表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
role_id: int | None = Field(default=None, description='角色ID')
role_name: str | None = Field(default=None, description='角色名称')
role_key: str | None = Field(default=None, description='角色权限字符串')
role_sort: int | None = Field(default=None, description='显示顺序')
data_scope: Literal['1', '2', '3', '4', '5'] | None = Field(
default=None,
description='数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限)',
)
menu_check_strictly: int | bool | None = Field(default=None, description='菜单树选择项是否关联显示')
dept_check_strictly: int | bool | None = Field(default=None, description='部门树选择项是否关联显示')
status: Literal['0', '1'] | None = Field(default=None, description='角色状态(0正常 1停用)')
del_flag: Literal['0', '2'] | None = Field(default=None, description='删除标志(0代表存在 2代表删除)')
create_by: str | None = Field(default=None, description='创建者')
create_time: datetime | None = Field(default=None, description='创建时间')
update_by: str | None = Field(default=None, description='更新者')
update_time: datetime | None = Field(default=None, description='更新时间')
remark: str | None = Field(default=None, description='备注')
admin: bool | None = Field(default=False, description='是否为admin')
@field_validator('menu_check_strictly', 'dept_check_strictly')
@classmethod
def check_filed_mapping(cls, v: int | bool) -> int | bool:
if v == 1:
v = True
elif v == 0:
v = False
elif v is True:
v = 1
elif v is False:
v = 0
return v
@model_validator(mode='after')
def check_admin(self) -> 'RoleModel':
if self.role_id == 1:
self.admin = True
else:
self.admin = False
return self
@NotBlank(field_name='role_name', message='角色名称不能为空')
@Size(field_name='role_name', min_length=0, max_length=30, message='角色名称长度不能超过30个字符')
def get_role_name(self) -> str | None:
return self.role_name
@NotBlank(field_name='role_key', message='权限字符不能为空')
@Size(field_name='role_key', min_length=0, max_length=100, message='权限字符长度不能超过100个字符')
def get_role_key(self) -> str | None:
return self.role_key
@NotBlank(field_name='role_sort', message='显示顺序不能为空')
def get_role_sort(self) -> int | None:
return self.role_sort
def validate_fields(self) -> None:
self.get_role_name()
self.get_role_key()
self.get_role_sort()
class RoleMenuModel(BaseModel):
"""
角色和菜单关联表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
role_id: int | None = Field(default=None, description='角色ID')
menu_id: int | None = Field(default=None, description='菜单ID')
class RoleDeptModel(BaseModel):
"""
角色和部门关联表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
role_id: int | None = Field(default=None, description='角色ID')
dept_id: int | None = Field(default=None, description='部门ID')
class RoleQueryModel(RoleModel):
"""
角色管理不分页查询模型
"""
begin_time: str | None = Field(default=None, description='开始时间')
end_time: str | None = Field(default=None, description='结束时间')
class RolePageQueryModel(RoleQueryModel):
"""
角色管理分页查询模型
"""
page_num: int = Field(default=1, description='当前页码')
page_size: int = Field(default=10, description='每页记录数')
class RoleMenuQueryModel(BaseModel):
"""
角色菜单查询模型
"""
model_config = ConfigDict(alias_generator=to_camel)
menus: list = Field(default=[], description='菜单信息')
checked_keys: list[int] = Field(default=[], description='已选择的菜单ID信息')
class RoleDeptQueryModel(BaseModel):
"""
角色部门查询模型
"""
model_config = ConfigDict(alias_generator=to_camel)
depts: list = Field(default=[], description='部门信息')
checked_keys: list[int] = Field(default=[], description='已选择的部门ID信息')
class AddRoleModel(RoleModel):
"""
新增角色模型
"""
dept_ids: list = Field(default=[], description='部门ID信息')
menu_ids: list = Field(default=[], description='菜单ID信息')
type: str | None = Field(default=None, description='操作类型')
class DeleteRoleModel(BaseModel):
"""
删除角色模型
"""
model_config = ConfigDict(alias_generator=to_camel)
role_ids: str = Field(description='需要删除的角色ID')
update_by: str | None = Field(default=None, description='更新者')
update_time: datetime | None = Field(default=None, description='更新时间')
@@ -0,0 +1,66 @@
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
class CpuInfo(BaseModel):
model_config = ConfigDict(alias_generator=to_camel)
cpu_num: int | None = Field(default=None, description='核心数')
used: float | None = Field(default=None, description='CPU用户使用率')
sys: float | None = Field(default=None, description='CPU系统使用率')
free: float | None = Field(default=None, description='CPU当前空闲率')
class MemoryInfo(BaseModel):
model_config = ConfigDict(alias_generator=to_camel)
total: str | None = Field(default=None, description='内存总量')
used: str | None = Field(default=None, description='已用内存')
free: str | None = Field(default=None, description='剩余内存')
usage: float | None = Field(default=None, description='使用率')
class SysInfo(BaseModel):
model_config = ConfigDict(alias_generator=to_camel)
computer_ip: str | None = Field(default=None, description='服务器IP')
computer_name: str | None = Field(default=None, description='服务器名称')
os_arch: str | None = Field(default=None, description='系统架构')
os_name: str | None = Field(default=None, description='操作系统')
user_dir: str | None = Field(default=None, description='项目路径')
class PyInfo(MemoryInfo):
model_config = ConfigDict(alias_generator=to_camel)
name: str | None = Field(default=None, description='Python名称')
version: str | None = Field(default=None, description='Python版本')
start_time: str | None = Field(default=None, description='启动时间')
run_time: str | None = Field(default=None, description='运行时长')
home: str | None = Field(default=None, description='安装路径')
class SysFiles(BaseModel):
model_config = ConfigDict(alias_generator=to_camel)
dir_name: str | None = Field(default=None, description='盘符路径')
sys_type_name: str | None = Field(default=None, description='盘符类型')
type_name: str | None = Field(default=None, description='文件类型')
total: str | None = Field(default=None, description='总大小')
used: str | None = Field(default=None, description='已经使用量')
free: str | None = Field(default=None, description='剩余大小')
usage: str | None = Field(default=None, description='资源的使用率')
class ServerMonitorModel(BaseModel):
"""
服务监控对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel)
cpu: CpuInfo | None = Field(description='CPU相关信息')
py: PyInfo | None = Field(description='Python相关信息')
mem: MemoryInfo | None = Field(description='內存相关信息')
sys: SysInfo | None = Field(description='服务器相关信息')
sys_files: list[SysFiles] | None = Field(description='磁盘相关信息')
@@ -0,0 +1,99 @@
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
class TransportCryptoFrontendConfigModel(BaseModel):
"""
传输层加解密前端运行配置模型
"""
model_config = ConfigDict(alias_generator=to_camel)
transport_crypto_enabled: bool = Field(description='后端是否启用传输层加解密')
transport_crypto_mode: str = Field(description='当前传输层加解密模式')
transport_crypto_active: bool = Field(description='前端当前是否应启用传输层加解密')
envelope_version: str = Field(description='当前传输层加密信封协议版本')
public_key_url: str = Field(description='传输层公钥接口路径')
request_envelope_algorithm: str = Field(description='前端请求信封算法标识')
response_envelope_algorithm: str = Field(description='前端响应信封算法标识')
enabled_paths: list[str] = Field(description='启用传输层加解密的路径列表')
required_paths: list[str] = Field(description='强制要求加密传输的路径列表')
exclude_paths: list[str] = Field(description='排除传输层加解密的路径列表')
max_encrypted_get_url_length: int = Field(description='前端执行加密GET/DELETE请求时允许的最大URL长度')
config_expire_at: int = Field(description='前端配置建议刷新时间戳')
class TransportCryptoPublicKeyModel(BaseModel):
"""
传输层加解密公钥下发模型
"""
model_config = ConfigDict(alias_generator=to_camel)
kid: str = Field(description='当前启用的密钥版本标识')
envelope_version: str = Field(description='当前传输层加密信封协议版本')
alg: str = Field(description='当前传输层加密算法标识')
public_key: str = Field(description='当前可用的传输层公钥')
supported_kids: list[str] = Field(description='当前支持解密的密钥版本列表')
expire_at: int = Field(description='当前公钥建议刷新时间戳')
class TransportCryptoKidStatModel(BaseModel):
"""
传输层加解密按密钥版本统计模型
"""
model_config = ConfigDict(alias_generator=to_camel)
kid: str | None = Field(default=None, description='密钥版本标识')
encrypted_requests: int | None = Field(default=0, description='加密请求次数')
decrypt_success: int | None = Field(default=0, description='请求解密成功次数')
decrypt_failure: int | None = Field(default=0, description='请求解密失败次数')
encrypted_responses: int | None = Field(default=0, description='加密响应次数')
class TransportCryptoFailureRecordModel(BaseModel):
"""
传输层加解密最近失败记录模型
"""
model_config = ConfigDict(alias_generator=to_camel)
time: datetime | None = Field(default=None, description='失败时间')
method: str | None = Field(default=None, description='请求方法')
path: str | None = Field(default=None, description='请求路径')
reason: str | None = Field(default=None, description='失败原因分类')
kid: str | None = Field(default=None, description='密钥版本标识')
class TransportCryptoMonitorModel(BaseModel):
"""
传输层加解密监控模型
"""
model_config = ConfigDict(alias_generator=to_camel)
monitor_scope: str | None = Field(default=None, description='监控统计范围,默认基于Redis聚合')
started_at: datetime | None = Field(default=None, description='当前监控统计起始时间')
app_env: str | None = Field(default=None, description='当前应用环境')
transport_crypto_enabled: bool | None = Field(default=None, description='是否启用传输层加解密')
transport_crypto_mode: str | None = Field(default=None, description='当前传输层加解密模式')
current_kid: str | None = Field(default=None, description='当前启用的密钥版本')
supported_kids: list[str] | None = Field(default=[], description='当前支持的密钥版本列表')
enabled_paths: list[str] | None = Field(default=[], description='启用传输层加解密的路径列表')
required_paths: list[str] | None = Field(default=[], description='强制要求加密传输的路径列表')
exclude_paths: list[str] | None = Field(default=[], description='排除传输层加解密的路径列表')
requests_total: int | None = Field(default=0, description='命中传输层加解密规则的请求总数')
plain_requests_total: int | None = Field(default=0, description='明文请求总数')
encrypted_requests_total: int | None = Field(default=0, description='加密请求总数')
required_rejected_total: int | None = Field(default=0, description='强制加密接口被拒绝的次数')
decrypt_success_total: int | None = Field(default=0, description='请求解密成功次数')
decrypt_failure_total: int | None = Field(default=0, description='请求解密失败次数')
plain_responses_total: int | None = Field(default=0, description='明文响应次数')
encrypted_responses_total: int | None = Field(default=0, description='加密响应次数')
encrypted_error_responses_total: int | None = Field(default=0, description='加密错误响应次数')
failure_reasons: dict[str, int] | None = Field(default={}, description='按失败原因归类的次数统计')
kid_stats: list[TransportCryptoKidStatModel] | None = Field(default=[], description='按密钥版本归类的统计信息')
recent_failures: list[TransportCryptoFailureRecordModel] | None = Field(default=[], description='最近失败事件列表')
@@ -0,0 +1,299 @@
import re
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, model_validator
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import Network, NotBlank, Size, Xss
from exceptions.exception import ModelValidatorException
from module_admin.entity.vo.dept_vo import DeptModel
from module_admin.entity.vo.post_vo import PostModel
from module_admin.entity.vo.role_vo import RoleModel
class TokenData(BaseModel):
"""
token解析结果
"""
user_id: int | None = Field(default=None, description='用户ID')
class UserModel(BaseModel):
"""
用户表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
user_id: int | None = Field(default=None, description='用户ID')
dept_id: int | None = Field(default=None, description='部门ID')
user_name: str | None = Field(default=None, description='用户账号')
nick_name: str | None = Field(default=None, description='用户昵称')
user_type: str | None = Field(default=None, description='用户类型(00系统用户)')
email: str | None = Field(default=None, description='用户邮箱')
phonenumber: str | None = Field(default=None, description='手机号码')
sex: Literal['0', '1', '2'] | None = Field(default=None, description='用户性别(0男 1女 2未知)')
avatar: str | None = Field(default=None, description='头像地址')
password: str | None = Field(default=None, description='密码')
status: Literal['0', '1'] | None = Field(default=None, description='帐号状态(0正常 1停用)')
del_flag: Literal['0', '2'] | None = Field(default=None, description='删除标志(0代表存在 2代表删除)')
login_ip: str | None = Field(default=None, description='最后登录IP')
login_date: datetime | None = Field(default=None, description='最后登录时间')
pwd_update_date: datetime | None = Field(default=None, description='密码最后更新时间')
create_by: str | None = Field(default=None, description='创建者')
create_time: datetime | None = Field(default=None, description='创建时间')
update_by: str | None = Field(default=None, description='更新者')
update_time: datetime | None = Field(default=None, description='更新时间')
remark: str | None = Field(default=None, description='备注')
admin: bool | None = Field(default=False, description='是否为admin')
@model_validator(mode='after')
def check_password(self) -> 'UserModel':
pattern = r"""^[^<>"'|\\]+$"""
if self.password is None or re.match(pattern, self.password):
return self
raise ModelValidatorException(message='密码不能包含非法字符:< > " \' \\ |')
@model_validator(mode='after')
def check_admin(self) -> 'UserModel':
if self.user_id == 1:
self.admin = True
else:
self.admin = False
return self
@Xss(field_name='user_name', message='用户账号不能包含脚本字符')
@NotBlank(field_name='user_name', message='用户账号不能为空')
@Size(field_name='user_name', min_length=0, max_length=30, message='用户账号长度不能超过30个字符')
def get_user_name(self) -> str | None:
return self.user_name
@Xss(field_name='nick_name', message='用户昵称不能包含脚本字符')
@Size(field_name='nick_name', min_length=0, max_length=30, message='用户昵称长度不能超过30个字符')
def get_nick_name(self) -> str | None:
return self.nick_name
@Network(field_name='email', field_type='EmailStr', message='邮箱格式不正确')
@Size(field_name='email', min_length=0, max_length=50, message='邮箱长度不能超过50个字符')
def get_email(self) -> str | None:
return self.email
@Size(field_name='phonenumber', min_length=0, max_length=11, message='手机号码长度不能超过11个字符')
def get_phonenumber(self) -> str | None:
return self.phonenumber
def validate_fields(self) -> None:
self.get_user_name()
self.get_nick_name()
self.get_email()
self.get_phonenumber()
class UserRowModel(UserModel):
"""
用户列表行数据模型
"""
dept: DeptModel | None = Field(default=None, description='部门信息')
class UserRoleModel(BaseModel):
"""
用户和角色关联表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
user_id: int | None = Field(default=None, description='用户ID')
role_id: int | None = Field(default=None, description='角色ID')
class UserPostModel(BaseModel):
"""
用户与岗位关联表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
user_id: int | None = Field(default=None, description='用户ID')
post_id: int | None = Field(default=None, description='岗位ID')
class UserInfoModel(UserModel):
post_ids: str | None | None = Field(default=None, description='岗位ID信息')
role_ids: str | None | None = Field(default=None, description='角色ID信息')
dept: DeptModel | None | None = Field(default=None, description='部门信息')
role: list[RoleModel | None] | None = Field(default=[], description='角色信息')
class CurrentUserModel(BaseModel):
model_config = ConfigDict(alias_generator=to_camel)
permissions: list = Field(description='权限信息')
roles: list = Field(description='角色信息')
user: UserInfoModel | None = Field(description='用户信息')
is_default_modify_pwd: bool = Field(default=False, description='是否初始密码修改提醒')
is_password_expired: bool = Field(default=False, description='密码是否过期提醒')
class UserDetailModel(BaseModel):
"""
获取用户详情信息响应模型
"""
model_config = ConfigDict(alias_generator=to_camel)
data: UserInfoModel | None | None = Field(default=None, description='用户信息')
post_ids: list | None = Field(default=None, description='岗位ID信息')
posts: list[PostModel | None] = Field(description='岗位信息')
role_ids: list | None = Field(default=None, description='角色ID信息')
roles: list[RoleModel | None] = Field(description='角色信息')
class UserProfileModel(BaseModel):
"""
获取个人信息响应模型
"""
model_config = ConfigDict(alias_generator=to_camel)
data: UserInfoModel | None = Field(description='用户信息')
post_group: str | None = Field(description='岗位信息')
role_group: str | None = Field(description='角色信息')
class AvatarModel(BaseModel):
"""
上传头像响应模型
"""
model_config = ConfigDict(alias_generator=to_camel)
img_url: str = Field(description='头像地址')
class UserQueryModel(UserModel):
"""
用户管理不分页查询模型
"""
begin_time: str | None = Field(default=None, description='开始时间')
end_time: str | None = Field(default=None, description='结束时间')
class UserPageQueryModel(UserQueryModel):
"""
用户管理分页查询模型
"""
page_num: int = Field(default=1, description='当前页码')
page_size: int = Field(default=10, description='每页记录数')
class AddUserModel(UserModel):
"""
新增用户模型
"""
role_ids: list | None = Field(default=[], description='角色ID信息')
post_ids: list | None = Field(default=[], description='岗位ID信息')
type: str | None = Field(default=None, description='操作类型')
class EditUserModel(AddUserModel):
"""
编辑用户模型
"""
role: list | None = Field(default=[], description='角色信息')
class ResetPasswordModel(BaseModel):
"""
重置密码模型
"""
model_config = ConfigDict(alias_generator=to_camel)
old_password: str | None = Field(default=None, description='旧密码')
new_password: str | None = Field(default=None, description='新密码')
@model_validator(mode='after')
def check_new_password(self) -> 'ResetPasswordModel':
pattern = r"""^[^<>"'|\\]+$"""
if self.new_password is None or re.match(pattern, self.new_password):
return self
raise ModelValidatorException(message='密码不能包含非法字符:< > " \' \\ |')
class ResetUserModel(UserModel):
"""
重置用户密码模型
"""
old_password: str | None = Field(default=None, description='旧密码')
sms_code: str | None = Field(default=None, description='验证码')
session_id: str | None = Field(default=None, description='会话id')
class DeleteUserModel(BaseModel):
"""
删除用户模型
"""
model_config = ConfigDict(alias_generator=to_camel)
user_ids: str = Field(description='需要删除的用户ID')
update_by: str | None = Field(default=None, description='更新者')
update_time: datetime | None = Field(default=None, description='更新时间')
class UserRoleQueryModel(UserModel):
"""
用户角色关联管理不分页查询模型
"""
role_id: int | None = Field(default=None, description='角色ID')
class UserRolePageQueryModel(UserRoleQueryModel):
"""
用户角色关联管理分页查询模型
"""
page_num: int = Field(default=1, description='当前页码')
page_size: int = Field(default=10, description='每页记录数')
class SelectedRoleModel(RoleModel):
"""
是否选择角色模型
"""
flag: bool | None = Field(default=False, description='选择标识')
class UserRoleResponseModel(BaseModel):
"""
用户角色关联管理列表返回模型
"""
model_config = ConfigDict(alias_generator=to_camel)
roles: list[SelectedRoleModel | None] = Field(default=[], description='角色信息')
user: UserInfoModel = Field(description='用户信息')
class CrudUserRoleModel(BaseModel):
"""
新增、删除用户关联角色及角色关联用户模型
"""
model_config = ConfigDict(alias_generator=to_camel)
user_id: int | None = Field(default=None, description='用户ID')
user_ids: str | None = Field(default=None, description='用户ID信息')
role_id: int | None = Field(default=None, description='角色ID')
role_ids: str | None = Field(default=None, description='角色ID信息')
@@ -0,0 +1,126 @@
from fastapi import Request
from common.enums import RedisInitKeyConfig
from common.vo import CrudResponseModel
from config.get_redis import RedisUtil
from module_admin.entity.vo.cache_vo import CacheInfoModel, CacheMonitorModel
class CacheService:
"""
缓存监控模块服务层
"""
@classmethod
async def get_cache_monitor_statistical_info_services(cls, request: Request) -> CacheMonitorModel:
"""
获取缓存监控信息service
:param request: Request对象
:return: 缓存监控信息
"""
info = await request.app.state.redis.info()
db_size = await request.app.state.redis.dbsize()
command_stats_dict = await request.app.state.redis.info('commandstats')
command_stats = [
{'name': key.split('_')[1], 'value': str(value.get('calls'))} for key, value in command_stats_dict.items()
]
result = CacheMonitorModel(commandStats=command_stats, dbSize=db_size, info=info)
return result
@classmethod
async def get_cache_monitor_cache_name_services(cls) -> list[CacheInfoModel]:
"""
获取缓存名称列表信息service
:return: 缓存名称列表信息
"""
name_list = [
CacheInfoModel(
cacheKey='',
cacheName=key_config.key,
cacheValue='',
remark=key_config.remark,
)
for key_config in RedisInitKeyConfig
]
return name_list
@classmethod
async def get_cache_monitor_cache_key_services(cls, request: Request, cache_name: str) -> list[str]:
"""
获取缓存键名列表信息service
:param request: Request对象
:param cache_name: 缓存名称
:return: 缓存键名列表信息
"""
cache_keys: list[str] = await request.app.state.redis.keys(f'{cache_name}*')
cache_key_list = [key.split(':', 1)[1] for key in cache_keys if key.startswith(f'{cache_name}:')]
return cache_key_list
@classmethod
async def get_cache_monitor_cache_value_services(
cls, request: Request, cache_name: str, cache_key: str
) -> CacheInfoModel:
"""
获取缓存内容信息service
:param request: Request对象
:param cache_name: 缓存名称
:param cache_key: 缓存键名
:return: 缓存内容信息
"""
cache_value = await request.app.state.redis.get(f'{cache_name}:{cache_key}')
return CacheInfoModel(cacheKey=cache_key, cacheName=cache_name, cacheValue=cache_value, remark='')
@classmethod
async def clear_cache_monitor_cache_name_services(cls, request: Request, cache_name: str) -> CrudResponseModel:
"""
清除缓存名称对应所有键值service
:param request: Request对象
:param cache_name: 缓存名称
:return: 操作缓存响应信息
"""
cache_keys = await request.app.state.redis.keys(f'{cache_name}*')
if cache_keys:
await request.app.state.redis.delete(*cache_keys)
return CrudResponseModel(is_success=True, message=f'{cache_name}对应键值清除成功')
@classmethod
async def clear_cache_monitor_cache_key_services(cls, request: Request, cache_key: str) -> CrudResponseModel:
"""
清除缓存名称对应所有键值service
:param request: Request对象
:param cache_key: 缓存键名
:return: 操作缓存响应信息
"""
cache_keys = await request.app.state.redis.keys(f'*{cache_key}')
if cache_keys:
await request.app.state.redis.delete(*cache_keys)
return CrudResponseModel(is_success=True, message=f'{cache_key}清除成功')
@classmethod
async def clear_cache_monitor_all_services(cls, request: Request) -> CrudResponseModel:
"""
清除所有缓存service
:param request: Request对象
:return: 操作缓存响应信息
"""
cache_keys = await request.app.state.redis.keys()
if cache_keys:
await request.app.state.redis.delete(*cache_keys)
await RedisUtil.init_sys_dict(request.app.state.redis)
await RedisUtil.init_sys_config(request.app.state.redis)
return CrudResponseModel(is_success=True, message='所有缓存清除成功')
@@ -0,0 +1,54 @@
import base64
import io
import random
import anyio
from PIL import Image, ImageDraw, ImageFont
class CaptchaService:
"""
验证码模块服务层
"""
@classmethod
async def create_captcha_image_service(cls) -> list[str, int]:
# 创建空白图像
image = Image.new('RGB', (160, 60), color='#EAEAEA')
# 创建绘图对象
draw = ImageDraw.Draw(image)
# 设置字体
font_path = (await anyio.Path.cwd()) / 'assets' / 'font' / 'Arial.ttf'
font = ImageFont.truetype(font_path, size=30)
# 生成两个0-9之间的随机整数
num1 = random.randint(0, 9)
num2 = random.randint(0, 9)
# 从运算符列表中随机选择一个
operational_character_list = ['+', '-', '*']
operational_character = random.choice(operational_character_list)
# 根据选择的运算符进行计算
if operational_character == '+':
result = num1 + num2
elif operational_character == '-':
result = num1 - num2
# 非负数结果的验证码更友好一些
if result < 0:
num1, num2 = num2, num1
result = num1 - num2
else:
result = num1 * num2
# 绘制文本
text = f'{num1} {operational_character} {num2} = ?'
draw.text((25, 15), text, fill='blue', font=font)
# 将图像数据保存到内存中
buffer = io.BytesIO()
image.save(buffer, format='PNG')
# 将图像数据转换为base64字符串
base64_string = base64.b64encode(buffer.getvalue()).decode()
return [base64_string, result]
@@ -0,0 +1,99 @@
import os
from datetime import datetime
import aiofiles
from fastapi import BackgroundTasks, Request, UploadFile
from common.vo import CrudResponseModel
from config.env import UploadConfig
from exceptions.exception import ServiceException
from module_admin.entity.vo.common_vo import UploadResponseModel
from utils.upload_util import UploadUtil
class CommonService:
"""
通用模块服务层
"""
@classmethod
async def upload_service(cls, request: Request, file: UploadFile) -> CrudResponseModel:
"""
通用上传service
:param request: Request对象
:param file: 上传文件对象
:return: 上传结果
"""
if not UploadUtil.check_file_extension(file):
raise ServiceException(message='文件类型不合法')
relative_path = (
f'upload/{datetime.now().strftime("%Y")}/{datetime.now().strftime("%m")}/{datetime.now().strftime("%d")}'
)
dir_path = os.path.join(UploadConfig.UPLOAD_PATH, relative_path)
try:
os.makedirs(dir_path)
except FileExistsError:
pass
filename = f'{file.filename.rsplit(".", 1)[0]}_{datetime.now().strftime("%Y%m%d%H%M%S")}{UploadConfig.UPLOAD_MACHINE}{UploadUtil.generate_random_number()}.{file.filename.rsplit(".")[-1]}'
filepath = os.path.join(dir_path, filename)
async with aiofiles.open(filepath, 'wb') as f:
# 流式写出大型文件,这里的10代表10MB
while True:
chunk = await file.read(1024 * 1024 * 10)
if not chunk:
break
await f.write(chunk)
return CrudResponseModel(
is_success=True,
result=UploadResponseModel(
fileName=f'{UploadConfig.UPLOAD_PREFIX}/{relative_path}/{filename}',
newFileName=filename,
originalFilename=file.filename,
url=f'{request.base_url}{UploadConfig.UPLOAD_PREFIX[1:]}/{relative_path}/{filename}',
),
message='上传成功',
)
@classmethod
async def download_services(
cls, background_tasks: BackgroundTasks, file_name: str, delete: bool
) -> CrudResponseModel:
"""
下载下载目录文件service
:param background_tasks: 后台任务对象
:param file_name: 下载的文件名称
:param delete: 是否在下载完成后删除文件
:return: 上传结果
"""
filepath = os.path.join(UploadConfig.DOWNLOAD_PATH, file_name)
if '..' in file_name:
raise ServiceException(message='文件名称不合法')
if not UploadUtil.check_file_exists(filepath):
raise ServiceException(message='文件不存在')
if delete:
background_tasks.add_task(UploadUtil.delete_file, filepath)
return CrudResponseModel(is_success=True, result=UploadUtil.generate_file(filepath), message='下载成功')
@classmethod
async def download_resource_services(cls, resource: str) -> CrudResponseModel:
"""
下载上传目录文件service
:param resource: 下载的文件名称
:return: 上传结果
"""
filepath = os.path.join(resource.replace(UploadConfig.UPLOAD_PREFIX, UploadConfig.UPLOAD_PATH))
filename = resource.rsplit('/', 1)[-1]
if (
'..' in filename
or not UploadUtil.check_file_timestamp(filename)
or not UploadUtil.check_file_machine(filename)
or not UploadUtil.check_file_random_code(filename)
):
raise ServiceException(message='文件名称不合法')
if not UploadUtil.check_file_exists(filepath):
raise ServiceException(message='文件不存在')
return CrudResponseModel(is_success=True, result=UploadUtil.generate_file(filepath), message='下载成功')
@@ -0,0 +1,234 @@
from typing import Any
from fastapi import Request
from redis import asyncio as aioredis
from sqlalchemy.ext.asyncio import AsyncSession
from common.constant import CommonConstant
from common.enums import RedisInitKeyConfig
from common.vo import CrudResponseModel, PageModel
from exceptions.exception import ServiceException
from module_admin.dao.config_dao import ConfigDao
from module_admin.entity.vo.config_vo import ConfigModel, ConfigPageQueryModel, DeleteConfigModel
from utils.common_util import CamelCaseUtil
from utils.excel_util import ExcelUtil
class ConfigService:
"""
参数配置管理模块服务层
"""
@classmethod
async def get_config_list_services(
cls, query_db: AsyncSession, query_object: ConfigPageQueryModel, is_page: bool = False
) -> PageModel | list[dict[str, Any]]:
"""
获取参数配置列表信息service
:param query_db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 参数配置列表信息对象
"""
config_list_result = await ConfigDao.get_config_list(query_db, query_object, is_page)
return config_list_result
@classmethod
async def init_cache_sys_config_services(cls, query_db: AsyncSession, redis: aioredis.Redis) -> None:
"""
应用初始化:获取所有参数配置对应的键值对信息并缓存service
:param query_db: orm对象
:param redis: redis对象
:return:
"""
# 获取以sys_config:开头的键列表
keys = await redis.keys(f'{RedisInitKeyConfig.SYS_CONFIG.key}:*')
# 删除匹配的键
if keys:
await redis.delete(*keys)
config_all = await ConfigDao.get_config_list(query_db, ConfigPageQueryModel(), is_page=False)
for config_obj in config_all:
await redis.set(
f'{RedisInitKeyConfig.SYS_CONFIG.key}:{config_obj.get("configKey")}',
config_obj.get('configValue'),
)
@classmethod
async def query_config_list_from_cache_services(cls, redis: aioredis.Redis, config_key: str) -> Any:
"""
从缓存获取参数键名对应值service
:param redis: redis对象
:param config_key: 参数键名
:return: 参数键名对应值
"""
result = await redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:{config_key}')
return result
@classmethod
async def check_config_key_unique_services(cls, query_db: AsyncSession, page_object: ConfigModel) -> bool:
"""
校验参数键名是否唯一service
:param query_db: orm对象
:param page_object: 参数配置对象
:return: 校验结果
"""
config_id = -1 if page_object.config_id is None else page_object.config_id
config = await ConfigDao.get_config_detail_by_info(query_db, ConfigModel(configKey=page_object.config_key))
if config and config.config_id != config_id:
return CommonConstant.NOT_UNIQUE
return CommonConstant.UNIQUE
@classmethod
async def add_config_services(
cls, request: Request, query_db: AsyncSession, page_object: ConfigModel
) -> CrudResponseModel:
"""
新增参数配置信息service
:param request: Request对象
:param query_db: orm对象
:param page_object: 新增参数配置对象
:return: 新增参数配置校验结果
"""
if not await cls.check_config_key_unique_services(query_db, page_object):
raise ServiceException(message=f'新增参数{page_object.config_name}失败,参数键名已存在')
try:
await ConfigDao.add_config_dao(query_db, page_object)
await query_db.commit()
await request.app.state.redis.set(
f'{RedisInitKeyConfig.SYS_CONFIG.key}:{page_object.config_key}', page_object.config_value
)
return CrudResponseModel(is_success=True, message='新增成功')
except Exception as e:
await query_db.rollback()
raise e
@classmethod
async def edit_config_services(
cls, request: Request, query_db: AsyncSession, page_object: ConfigModel
) -> CrudResponseModel:
"""
编辑参数配置信息service
:param request: Request对象
:param query_db: orm对象
:param page_object: 编辑参数配置对象
:return: 编辑参数配置校验结果
"""
edit_config = page_object.model_dump(exclude_unset=True)
config_info = await cls.config_detail_services(query_db, page_object.config_id)
if config_info.config_id:
if not await cls.check_config_key_unique_services(query_db, page_object):
raise ServiceException(message=f'修改参数{page_object.config_name}失败,参数键名已存在')
try:
await ConfigDao.edit_config_dao(query_db, edit_config)
await query_db.commit()
if config_info.config_key != page_object.config_key:
await request.app.state.redis.delete(
f'{RedisInitKeyConfig.SYS_CONFIG.key}:{config_info.config_key}'
)
await request.app.state.redis.set(
f'{RedisInitKeyConfig.SYS_CONFIG.key}:{page_object.config_key}', page_object.config_value
)
return CrudResponseModel(is_success=True, message='更新成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='参数配置不存在')
@classmethod
async def delete_config_services(
cls, request: Request, query_db: AsyncSession, page_object: DeleteConfigModel
) -> CrudResponseModel:
"""
删除参数配置信息service
:param request: Request对象
:param query_db: orm对象
:param page_object: 删除参数配置对象
:return: 删除参数配置校验结果
"""
if page_object.config_ids:
config_id_list = page_object.config_ids.split(',')
try:
delete_config_key_list = []
for config_id in config_id_list:
config_info = await cls.config_detail_services(query_db, int(config_id))
if config_info.config_type == CommonConstant.YES:
raise ServiceException(message=f'内置参数{config_info.config_key}不能删除')
await ConfigDao.delete_config_dao(query_db, ConfigModel(configId=int(config_id)))
delete_config_key_list.append(f'{RedisInitKeyConfig.SYS_CONFIG.key}:{config_info.config_key}')
await query_db.commit()
if delete_config_key_list:
await request.app.state.redis.delete(*delete_config_key_list)
return CrudResponseModel(is_success=True, message='删除成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='传入参数配置id为空')
@classmethod
async def config_detail_services(cls, query_db: AsyncSession, config_id: int) -> ConfigModel:
"""
获取参数配置详细信息service
:param query_db: orm对象
:param config_id: 参数配置id
:return: 参数配置id对应的信息
"""
config = await ConfigDao.get_config_detail_by_id(query_db, config_id=config_id)
result = ConfigModel(**CamelCaseUtil.transform_result(config)) if config else ConfigModel()
return result
@staticmethod
async def export_config_list_services(config_list: list) -> bytes:
"""
导出参数配置信息service
:param config_list: 参数配置信息列表
:return: 参数配置信息对应excel的二进制数据
"""
# 创建一个映射字典,将英文键映射到中文键
mapping_dict = {
'configId': '参数主键',
'configName': '参数名称',
'configKey': '参数键名',
'configValue': '参数键值',
'configType': '系统内置',
'createBy': '创建者',
'createTime': '创建时间',
'updateBy': '更新者',
'updateTime': '更新时间',
'remark': '备注',
}
for item in config_list:
if item.get('configType') == 'Y':
item['configType'] = '是'
else:
item['configType'] = '否'
binary_data = ExcelUtil.export_list2excel(config_list, mapping_dict)
return binary_data
@classmethod
async def refresh_sys_config_services(cls, request: Request, query_db: AsyncSession) -> CrudResponseModel:
"""
刷新字典缓存信息service
:param request: Request对象
:param query_db: orm对象
:return: 刷新字典缓存校验结果
"""
await cls.init_cache_sys_config_services(query_db, request.app.state.redis)
return CrudResponseModel(is_success=True, message='刷新成功')
@@ -0,0 +1,285 @@
from collections.abc import Sequence
from typing import Any
from sqlalchemy import ColumnElement
from sqlalchemy.ext.asyncio import AsyncSession
from common.constant import CommonConstant
from common.vo import CrudResponseModel
from exceptions.exception import ServiceException, ServiceWarning
from module_admin.dao.dept_dao import DeptDao
from module_admin.entity.do.dept_do import SysDept
from module_admin.entity.vo.dept_vo import DeleteDeptModel, DeptModel, DeptTreeModel
from utils.common_util import CamelCaseUtil
class DeptService:
"""
部门管理模块服务层
"""
@classmethod
async def get_dept_tree_services(
cls, query_db: AsyncSession, page_object: DeptModel, data_scope_sql: ColumnElement
) -> list[dict[str, Any]]:
"""
获取部门树信息service
:param query_db: orm对象
:param page_object: 查询参数对象
:param data_scope_sql: 数据权限对应的查询sql语句
:return: 部门树信息对象
"""
dept_list_result = await DeptDao.get_dept_list_for_tree(query_db, page_object, data_scope_sql)
dept_tree_model_result = cls.list_to_tree(dept_list_result)
dept_tree_result = [dept.model_dump(exclude_unset=True, by_alias=True) for dept in dept_tree_model_result]
return dept_tree_result
@classmethod
async def get_dept_for_edit_option_services(
cls, query_db: AsyncSession, page_object: DeptModel, data_scope_sql: ColumnElement
) -> list[dict[str, Any]]:
"""
获取部门编辑部门树信息service
:param query_db: orm对象
:param page_object: 查询参数对象
:param data_scope_sql: 数据权限对应的查询sql语句
:return: 部门树信息对象
"""
dept_list_result = await DeptDao.get_dept_info_for_edit_option(query_db, page_object, data_scope_sql)
return CamelCaseUtil.transform_result(dept_list_result)
@classmethod
async def get_dept_list_services(
cls, query_db: AsyncSession, page_object: DeptModel, data_scope_sql: ColumnElement
) -> list[dict[str, Any]]:
"""
获取部门列表信息service
:param query_db: orm对象
:param page_object: 分页查询参数对象
:param data_scope_sql: 数据权限对应的查询sql语句
:return: 部门列表信息对象
"""
dept_list_result = await DeptDao.get_dept_list(query_db, page_object, data_scope_sql)
return CamelCaseUtil.transform_result(dept_list_result)
@classmethod
async def check_dept_data_scope_services(
cls, query_db: AsyncSession, dept_id: int, data_scope_sql: ColumnElement
) -> CrudResponseModel:
"""
校验部门是否有数据权限service
:param query_db: orm对象
:param dept_id: 部门id
:param data_scope_sql: 数据权限对应的查询sql语句
:return: 校验结果
"""
depts = await DeptDao.get_dept_list(query_db, DeptModel(deptId=dept_id), data_scope_sql)
if depts:
return CrudResponseModel(is_success=True, message='校验通过')
raise ServiceException(message='没有权限访问部门数据')
@classmethod
async def check_dept_name_unique_services(cls, query_db: AsyncSession, page_object: DeptModel) -> bool:
"""
校验部门名称是否唯一service
:param query_db: orm对象
:param page_object: 部门对象
:return: 校验结果
"""
dept_id = -1 if page_object.dept_id is None else page_object.dept_id
dept = await DeptDao.get_dept_detail_by_info(
query_db, DeptModel(deptName=page_object.dept_name, parentId=page_object.parent_id)
)
if dept and dept.dept_id != dept_id:
return CommonConstant.NOT_UNIQUE
return CommonConstant.UNIQUE
@classmethod
async def add_dept_services(cls, query_db: AsyncSession, page_object: DeptModel) -> CrudResponseModel:
"""
新增部门信息service
:param query_db: orm对象
:param page_object: 新增部门对象
:return: 新增部门校验结果
"""
if not await cls.check_dept_name_unique_services(query_db, page_object):
raise ServiceException(message=f'新增部门{page_object.dept_name}失败,部门名称已存在')
parent_info = await DeptDao.get_dept_by_id(query_db, page_object.parent_id)
if parent_info.status != CommonConstant.DEPT_NORMAL:
raise ServiceException(message=f'部门{parent_info.dept_name}停用,不允许新增')
page_object.ancestors = f'{parent_info.ancestors},{page_object.parent_id}'
try:
await DeptDao.add_dept_dao(query_db, page_object)
await query_db.commit()
return CrudResponseModel(is_success=True, message='新增成功')
except Exception as e:
await query_db.rollback()
raise e
@classmethod
async def edit_dept_services(cls, query_db: AsyncSession, page_object: DeptModel) -> CrudResponseModel:
"""
编辑部门信息service
:param query_db: orm对象
:param page_object: 编辑部门对象
:return: 编辑部门校验结果
"""
if not await cls.check_dept_name_unique_services(query_db, page_object):
raise ServiceException(message=f'修改部门{page_object.dept_name}失败,部门名称已存在')
if page_object.dept_id == page_object.parent_id:
raise ServiceException(message=f'修改部门{page_object.dept_name}失败,上级部门不能是自己')
if (
page_object.status == CommonConstant.DEPT_DISABLE
and (await DeptDao.count_normal_children_dept_dao(query_db, page_object.dept_id)) > 0
):
raise ServiceException(message=f'修改部门{page_object.dept_name}失败,该部门包含未停用的子部门')
new_parent_dept = await DeptDao.get_dept_by_id(query_db, page_object.parent_id)
old_dept = await DeptDao.get_dept_by_id(query_db, page_object.dept_id)
try:
if new_parent_dept and old_dept:
new_ancestors = f'{new_parent_dept.ancestors},{new_parent_dept.dept_id}'
old_ancestors = old_dept.ancestors
page_object.ancestors = new_ancestors
await cls.update_dept_children(query_db, page_object.dept_id, new_ancestors, old_ancestors)
edit_dept = page_object.model_dump(exclude_unset=True)
await DeptDao.edit_dept_dao(query_db, edit_dept)
if (
page_object.status == CommonConstant.DEPT_NORMAL
and page_object.ancestors
and page_object.ancestors != 0
):
await cls.update_parent_dept_status_normal(query_db, page_object)
await query_db.commit()
return CrudResponseModel(is_success=True, message='更新成功')
except Exception as e:
await query_db.rollback()
raise e
@classmethod
async def delete_dept_services(cls, query_db: AsyncSession, page_object: DeleteDeptModel) -> CrudResponseModel:
"""
删除部门信息service
:param query_db: orm对象
:param page_object: 删除部门对象
:return: 删除部门校验结果
"""
if page_object.dept_ids:
dept_id_list = page_object.dept_ids.split(',')
try:
for dept_id in dept_id_list:
if (await DeptDao.count_children_dept_dao(query_db, int(dept_id))) > 0:
raise ServiceWarning(message='存在下级部门,不允许删除')
if (await DeptDao.count_dept_user_dao(query_db, int(dept_id))) > 0:
raise ServiceWarning(message='部门存在用户,不允许删除')
await DeptDao.delete_dept_dao(query_db, DeptModel(deptId=dept_id))
await query_db.commit()
return CrudResponseModel(is_success=True, message='删除成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='传入部门id为空')
@classmethod
async def dept_detail_services(cls, query_db: AsyncSession, dept_id: int) -> DeptModel:
"""
获取部门详细信息service
:param query_db: orm对象
:param dept_id: 部门id
:return: 部门id对应的信息
"""
dept = await DeptDao.get_dept_detail_by_id(query_db, dept_id=dept_id)
result = DeptModel(**CamelCaseUtil.transform_result(dept)) if dept else DeptModel()
return result
@classmethod
def list_to_tree(cls, permission_list: Sequence[SysDept]) -> list[DeptTreeModel]:
"""
工具方法:根据部门列表信息生成树形嵌套数据
:param permission_list: 部门列表信息
:return: 部门树形嵌套数据
"""
_permission_list = [
DeptTreeModel(id=item.dept_id, label=item.dept_name, parentId=item.parent_id) for item in permission_list
]
# 转成id为key的字典
mapping: dict[int, DeptTreeModel] = dict(zip([i.id for i in _permission_list], _permission_list, strict=False))
# 树容器
container: list[DeptTreeModel] = []
for d in _permission_list:
# 如果找不到父级项,则是根节点
parent = mapping.get(d.parent_id)
if parent is None:
container.append(d)
else:
children: list[DeptTreeModel] = parent.children
if not children:
children = []
children.append(d)
parent.children = children
return container
@classmethod
async def replace_first(cls, original_str: str, old_str: str, new_str: str) -> str:
"""
工具方法:替换字符串
:param original_str: 需要替换的原始字符串
:param old_str: 用于匹配的字符串
:param new_str: 替换的字符串
:return: 替换后的字符串
"""
if original_str.startswith(old_str):
return original_str.replace(old_str, new_str, 1)
return original_str
@classmethod
async def update_parent_dept_status_normal(cls, query_db: AsyncSession, dept: DeptModel) -> None:
"""
更新父部门状态为正常
:param query_db: orm对象
:param dept: 部门对象
:return:
"""
dept_id_list = dept.ancestors.split(',')
await DeptDao.update_dept_status_normal_dao(query_db, list(map(int, dept_id_list)))
@classmethod
async def update_dept_children(
cls, query_db: AsyncSession, dept_id: int, new_ancestors: str, old_ancestors: str
) -> None:
"""
更新子部门信息
:param query_db: orm对象
:param dept_id: 部门id
:param new_ancestors: 新的祖先
:param old_ancestors: 旧的祖先
:return:
"""
children = await DeptDao.get_children_dept_dao(query_db, dept_id)
update_children = []
for child in children:
child_ancestors = await cls.replace_first(child.ancestors, old_ancestors, new_ancestors)
update_children.append({'dept_id': child.dept_id, 'ancestors': child_ancestors})
if children:
await DeptDao.update_dept_children_dao(query_db, update_children)
@@ -0,0 +1,464 @@
import json
from collections.abc import Sequence
from typing import Any
from fastapi import Request
from redis import asyncio as aioredis
from sqlalchemy.ext.asyncio import AsyncSession
from common.constant import CommonConstant
from common.enums import RedisInitKeyConfig
from common.vo import CrudResponseModel, PageModel
from exceptions.exception import ServiceException
from module_admin.dao.dict_dao import DictDataDao, DictTypeDao
from module_admin.entity.do.dict_do import SysDictData
from module_admin.entity.vo.dict_vo import (
DeleteDictDataModel,
DeleteDictTypeModel,
DictDataModel,
DictDataPageQueryModel,
DictTypeModel,
DictTypePageQueryModel,
)
from utils.common_util import CamelCaseUtil
from utils.excel_util import ExcelUtil
class DictTypeService:
"""
字典类型管理模块服务层
"""
@classmethod
async def get_dict_type_list_services(
cls, query_db: AsyncSession, query_object: DictTypePageQueryModel, is_page: bool = False
) -> PageModel | list[dict[str, Any]]:
"""
获取字典类型列表信息service
:param query_db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 字典类型列表信息对象
"""
dict_type_list_result = await DictTypeDao.get_dict_type_list(query_db, query_object, is_page)
return dict_type_list_result
@classmethod
async def check_dict_type_unique_services(cls, query_db: AsyncSession, page_object: DictTypeModel) -> bool:
"""
校验字典类型称是否唯一service
:param query_db: orm对象
:param page_object: 字典类型对象
:return: 校验结果
"""
dict_id = -1 if page_object.dict_id is None else page_object.dict_id
dict_type = await DictTypeDao.get_dict_type_detail_by_info(
query_db, DictTypeModel(dictType=page_object.dict_type)
)
if dict_type and dict_type.dict_id != dict_id:
return CommonConstant.NOT_UNIQUE
return CommonConstant.UNIQUE
@classmethod
async def add_dict_type_services(
cls, request: Request, query_db: AsyncSession, page_object: DictTypeModel
) -> CrudResponseModel:
"""
新增字典类型信息service
:param request: Request对象
:param query_db: orm对象
:param page_object: 新增岗位对象
:return: 新增字典类型校验结果
"""
if not await cls.check_dict_type_unique_services(query_db, page_object):
raise ServiceException(message=f'新增字典{page_object.dict_name}失败,字典类型已存在')
try:
await DictTypeDao.add_dict_type_dao(query_db, page_object)
await query_db.commit()
await request.app.state.redis.set(f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}', '')
result = {'is_success': True, 'message': '新增成功'}
except Exception as e:
await query_db.rollback()
raise e
return CrudResponseModel(**result)
@classmethod
async def edit_dict_type_services(
cls, request: Request, query_db: AsyncSession, page_object: DictTypeModel
) -> CrudResponseModel:
"""
编辑字典类型信息service
:param request: Request对象
:param query_db: orm对象
:param page_object: 编辑字典类型对象
:return: 编辑字典类型校验结果
"""
edit_dict_type = page_object.model_dump(exclude_unset=True)
dict_type_info = await cls.dict_type_detail_services(query_db, page_object.dict_id)
if dict_type_info.dict_id:
if not await cls.check_dict_type_unique_services(query_db, page_object):
raise ServiceException(message=f'修改字典{page_object.dict_name}失败,字典类型已存在')
try:
query_dict_data = DictDataPageQueryModel(dictType=dict_type_info.dict_type)
dict_data_list = await DictDataDao.get_dict_data_list(query_db, query_dict_data, is_page=False)
if dict_type_info.dict_type != page_object.dict_type:
for dict_data in dict_data_list:
edit_dict_data = DictDataModel(
dictCode=dict_data.get('dict_code'),
dictType=page_object.dict_type,
updateBy=page_object.update_by,
updateTime=page_object.update_time,
).model_dump(exclude_unset=True)
await DictDataDao.edit_dict_data_dao(query_db, edit_dict_data)
await DictTypeDao.edit_dict_type_dao(query_db, edit_dict_type)
await query_db.commit()
if dict_type_info.dict_type != page_object.dict_type:
dict_data = [CamelCaseUtil.transform_result(row) for row in dict_data_list if row]
await request.app.state.redis.set(
f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}',
json.dumps(dict_data, ensure_ascii=False, default=str),
)
return CrudResponseModel(is_success=True, message='更新成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='字典类型不存在')
@classmethod
async def delete_dict_type_services(
cls, request: Request, query_db: AsyncSession, page_object: DeleteDictTypeModel
) -> CrudResponseModel:
"""
删除字典类型信息service
:param request: Request对象
:param query_db: orm对象
:param page_object: 删除字典类型对象
:return: 删除字典类型校验结果
"""
if page_object.dict_ids:
dict_id_list = page_object.dict_ids.split(',')
try:
delete_dict_type_list = []
for dict_id in dict_id_list:
dict_type_into = await cls.dict_type_detail_services(query_db, int(dict_id))
if (await DictDataDao.count_dict_data_dao(query_db, dict_type_into.dict_type)) > 0:
raise ServiceException(message=f'{dict_type_into.dict_name}已分配,不能删除')
await DictTypeDao.delete_dict_type_dao(query_db, DictTypeModel(dictId=int(dict_id)))
delete_dict_type_list.append(f'{RedisInitKeyConfig.SYS_DICT.key}:{dict_type_into.dict_type}')
await query_db.commit()
if delete_dict_type_list:
await request.app.state.redis.delete(*delete_dict_type_list)
return CrudResponseModel(is_success=True, message='删除成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='传入字典类型id为空')
@classmethod
async def dict_type_detail_services(cls, query_db: AsyncSession, dict_id: int) -> DictTypeModel:
"""
获取字典类型详细信息service
:param query_db: orm对象
:param dict_id: 字典类型id
:return: 字典类型id对应的信息
"""
dict_type = await DictTypeDao.get_dict_type_detail_by_id(query_db, dict_id=dict_id)
result = DictTypeModel(**CamelCaseUtil.transform_result(dict_type)) if dict_type else DictTypeModel()
return result
@staticmethod
async def export_dict_type_list_services(dict_type_list: list) -> bytes:
"""
导出字典类型信息service
:param dict_type_list: 字典信息列表
:return: 字典信息对应excel的二进制数据
"""
# 创建一个映射字典,将英文键映射到中文键
mapping_dict = {
'dictId': '字典编号',
'dictName': '字典名称',
'dictType': '字典类型',
'status': '状态',
'createBy': '创建者',
'createTime': '创建时间',
'updateBy': '更新者',
'updateTime': '更新时间',
'remark': '备注',
}
for item in dict_type_list:
if item.get('status') == '0':
item['status'] = '正常'
else:
item['status'] = '停用'
binary_data = ExcelUtil.export_list2excel(dict_type_list, mapping_dict)
return binary_data
@classmethod
async def refresh_sys_dict_services(cls, request: Request, query_db: AsyncSession) -> CrudResponseModel:
"""
刷新字典缓存信息service
:param request: Request对象
:param query_db: orm对象
:return: 刷新字典缓存校验结果
"""
await DictDataService.init_cache_sys_dict_services(query_db, request.app.state.redis)
result = {'is_success': True, 'message': '刷新成功'}
return CrudResponseModel(**result)
class DictDataService:
"""
字典数据管理模块服务层
"""
@classmethod
async def get_dict_data_list_services(
cls, query_db: AsyncSession, query_object: DictDataPageQueryModel, is_page: bool = False
) -> PageModel | list[dict[str, Any]]:
"""
获取字典数据列表信息service
:param query_db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 字典数据列表信息对象
"""
dict_data_list_result = await DictDataDao.get_dict_data_list(query_db, query_object, is_page)
return dict_data_list_result
@classmethod
async def query_dict_data_list_services(cls, query_db: AsyncSession, dict_type: str) -> Sequence[SysDictData]:
"""
获取字典数据列表信息service
:param query_db: orm对象
:param dict_type: 字典类型
:return: 字典数据列表信息对象
"""
dict_data_list_result = await DictDataDao.query_dict_data_list(query_db, dict_type)
return dict_data_list_result
@classmethod
async def init_cache_sys_dict_services(cls, query_db: AsyncSession, redis: aioredis.Redis) -> None:
"""
应用初始化:获取所有字典类型对应的字典数据信息并缓存service
:param query_db: orm对象
:param redis: redis对象
:return:
"""
# 获取以sys_dict:开头的键列表
keys = await redis.keys(f'{RedisInitKeyConfig.SYS_DICT.key}:*')
# 删除匹配的键
if keys:
await redis.delete(*keys)
dict_type_all = await DictTypeDao.get_all_dict_type(query_db)
for dict_type_obj in [item for item in dict_type_all if item.status == '0']:
dict_type = dict_type_obj.dict_type
dict_data_list = await DictDataDao.query_dict_data_list(query_db, dict_type)
dict_data = [CamelCaseUtil.transform_result(row) for row in dict_data_list if row]
await redis.set(
f'{RedisInitKeyConfig.SYS_DICT.key}:{dict_type}',
json.dumps(dict_data, ensure_ascii=False, default=str),
)
@classmethod
async def query_dict_data_list_from_cache_services(
cls, redis: aioredis.Redis, dict_type: str
) -> list[dict[str, Any]]:
"""
从缓存获取字典数据列表信息service
:param redis: redis对象
:param dict_type: 字典类型
:return: 字典数据列表信息对象
"""
result = []
dict_data_list_result = await redis.get(f'{RedisInitKeyConfig.SYS_DICT.key}:{dict_type}')
if dict_data_list_result:
result = json.loads(dict_data_list_result)
return CamelCaseUtil.transform_result(result)
@classmethod
async def check_dict_data_unique_services(cls, query_db: AsyncSession, page_object: DictDataModel) -> bool:
"""
校验字典数据是否唯一service
:param query_db: orm对象
:param page_object: 字典数据对象
:return: 校验结果
"""
dict_code = -1 if page_object.dict_code is None else page_object.dict_code
dict_data = await DictDataDao.get_dict_data_detail_by_info(query_db, page_object)
if dict_data and dict_data.dict_code != dict_code:
return CommonConstant.NOT_UNIQUE
return CommonConstant.UNIQUE
@classmethod
async def add_dict_data_services(
cls, request: Request, query_db: AsyncSession, page_object: DictDataModel
) -> CrudResponseModel:
"""
新增字典数据信息service
:param request: Request对象
:param query_db: orm对象
:param page_object: 新增岗位对象
:return: 新增字典数据校验结果
"""
if not await cls.check_dict_data_unique_services(query_db, page_object):
raise ServiceException(
message=f'新增字典数据{page_object.dict_label}失败,{page_object.dict_type}下已存在该字典数据'
)
try:
await DictDataDao.add_dict_data_dao(query_db, page_object)
await query_db.commit()
dict_data_list = await cls.query_dict_data_list_services(query_db, page_object.dict_type)
await request.app.state.redis.set(
f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}',
json.dumps(CamelCaseUtil.transform_result(dict_data_list), ensure_ascii=False, default=str),
)
return CrudResponseModel(is_success=True, message='新增成功')
except Exception as e:
await query_db.rollback()
raise e
@classmethod
async def edit_dict_data_services(
cls, request: Request, query_db: AsyncSession, page_object: DictDataModel
) -> CrudResponseModel:
"""
编辑字典数据信息service
:param request: Request对象
:param query_db: orm对象
:param page_object: 编辑字典数据对象
:return: 编辑字典数据校验结果
"""
edit_data_type = page_object.model_dump(exclude_unset=True)
dict_data_info = await cls.dict_data_detail_services(query_db, page_object.dict_code)
if dict_data_info.dict_code:
if not await cls.check_dict_data_unique_services(query_db, page_object):
raise ServiceException(
message=f'新增字典数据{page_object.dict_label}失败,{page_object.dict_type}下已存在该字典数据'
)
try:
await DictDataDao.edit_dict_data_dao(query_db, edit_data_type)
await query_db.commit()
dict_data_list = await cls.query_dict_data_list_services(query_db, page_object.dict_type)
await request.app.state.redis.set(
f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}',
json.dumps(CamelCaseUtil.transform_result(dict_data_list), ensure_ascii=False, default=str),
)
return CrudResponseModel(is_success=True, message='更新成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='字典数据不存在')
@classmethod
async def delete_dict_data_services(
cls, request: Request, query_db: AsyncSession, page_object: DeleteDictDataModel
) -> CrudResponseModel:
"""
删除字典数据信息service
:param request: Request对象
:param query_db: orm对象
:param page_object: 删除字典数据对象
:return: 删除字典数据校验结果
"""
if page_object.dict_codes:
dict_code_list = page_object.dict_codes.split(',')
try:
delete_dict_type_list = []
for dict_code in dict_code_list:
dict_data = await cls.dict_data_detail_services(query_db, int(dict_code))
await DictDataDao.delete_dict_data_dao(query_db, DictDataModel(dictCode=dict_code))
delete_dict_type_list.append(dict_data.dict_type)
await query_db.commit()
for dict_type in list(set(delete_dict_type_list)):
dict_data_list = await cls.query_dict_data_list_services(query_db, dict_type)
await request.app.state.redis.set(
f'{RedisInitKeyConfig.SYS_DICT.key}:{dict_type}',
json.dumps(CamelCaseUtil.transform_result(dict_data_list), ensure_ascii=False, default=str),
)
return CrudResponseModel(is_success=True, message='删除成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='传入字典数据id为空')
@classmethod
async def dict_data_detail_services(cls, query_db: AsyncSession, dict_code: int) -> DictDataModel:
"""
获取字典数据详细信息service
:param query_db: orm对象
:param dict_code: 字典数据id
:return: 字典数据id对应的信息
"""
dict_data = await DictDataDao.get_dict_data_detail_by_id(query_db, dict_code=dict_code)
result = DictDataModel(**CamelCaseUtil.transform_result(dict_data)) if dict_data else DictDataModel()
return result
@staticmethod
async def export_dict_data_list_services(dict_data_list: list) -> bytes:
"""
导出字典数据信息service
:param dict_data_list: 字典数据信息列表
:return: 字典数据信息对应excel的二进制数据
"""
# 创建一个映射字典,将英文键映射到中文键
mapping_dict = {
'dictCode': '字典编码',
'dictSort': '字典标签',
'dictLabel': '字典键值',
'dictValue': '字典排序',
'dictType': '字典类型',
'cssClass': '样式属性',
'listClass': '表格回显样式',
'isDefault': '是否默认',
'status': '状态',
'createBy': '创建者',
'createTime': '创建时间',
'updateBy': '更新者',
'updateTime': '更新时间',
'remark': '备注',
}
for item in dict_data_list:
if item.get('status') == '0':
item['status'] = '正常'
else:
item['status'] = '停用'
if item.get('isDefault') == 'Y':
item['isDefault'] = '是'
else:
item['isDefault'] = '否'
binary_data = ExcelUtil.export_list2excel(dict_data_list, mapping_dict)
return binary_data
@@ -0,0 +1,144 @@
from typing import Any
from fastapi import Request
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Session
from common.vo import CrudResponseModel, PageModel
from module_admin.dao.job_log_dao import JobLogDao
from module_admin.entity.vo.job_vo import DeleteJobLogModel, JobLogModel, JobLogPageQueryModel
from module_admin.service.dict_service import DictDataService
from utils.excel_util import ExcelUtil
class JobLogService:
"""
定时任务日志管理模块服务层
"""
@classmethod
async def get_job_log_list_services(
cls, query_db: AsyncSession, query_object: JobLogPageQueryModel, is_page: bool = False
) -> PageModel | list[dict[str, Any]]:
"""
获取定时任务日志列表信息service
:param query_db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 定时任务日志列表信息对象
"""
job_log_list_result = await JobLogDao.get_job_log_list(query_db, query_object, is_page)
return job_log_list_result
@classmethod
def add_job_log_services(cls, query_db: Session, page_object: JobLogModel) -> CrudResponseModel:
"""
新增定时任务日志信息service
:param query_db: orm对象
:param page_object: 新增定时任务日志对象
:return: 新增定时任务日志校验结果
"""
try:
JobLogDao.add_job_log_dao(query_db, page_object)
query_db.commit()
result = {'is_success': True, 'message': '新增成功'}
except Exception as e:
query_db.rollback()
result = {'is_success': False, 'message': str(e)}
return CrudResponseModel(**result)
@classmethod
async def delete_job_log_services(cls, query_db: AsyncSession, page_object: DeleteJobLogModel) -> CrudResponseModel:
"""
删除定时任务日志信息service
:param query_db: orm对象
:param page_object: 删除定时任务日志对象
:return: 删除定时任务日志校验结果
"""
if page_object.job_log_ids:
job_log_id_list = page_object.job_log_ids.split(',')
try:
for job_log_id in job_log_id_list:
await JobLogDao.delete_job_log_dao(query_db, JobLogModel(jobLogId=job_log_id))
await query_db.commit()
result = {'is_success': True, 'message': '删除成功'}
except Exception as e:
await query_db.rollback()
raise e
else:
result = {'is_success': False, 'message': '传入定时任务日志id为空'}
return CrudResponseModel(**result)
@classmethod
async def clear_job_log_services(cls, query_db: AsyncSession) -> CrudResponseModel:
"""
清除定时任务日志信息service
:param query_db: orm对象
:return: 清除定时任务日志校验结果
"""
try:
await JobLogDao.clear_job_log_dao(query_db)
await query_db.commit()
result = {'is_success': True, 'message': '清除成功'}
except Exception as e:
await query_db.rollback()
raise e
return CrudResponseModel(**result)
@staticmethod
async def export_job_log_list_services(request: Request, job_log_list: list) -> bytes:
"""
导出定时任务日志信息service
:param request: Request对象
:param job_log_list: 定时任务日志信息列表
:return: 定时任务日志信息对应excel的二进制数据
"""
# 创建一个映射字典,将英文键映射到中文键
mapping_dict = {
'jobLogId': '任务日志编码',
'jobName': '任务名称',
'jobGroup': '任务组名',
'jobExecutor': '任务执行器',
'invokeTarget': '调用目标字符串',
'jobArgs': '位置参数',
'jobKwargs': '关键字参数',
'jobTrigger': '任务触发器',
'jobMessage': '日志信息',
'status': '执行状态',
'exceptionInfo': '异常信息',
'createTime': '创建时间',
}
job_group_list = await DictDataService.query_dict_data_list_from_cache_services(
request.app.state.redis, dict_type='sys_job_group'
)
job_group_option = [{'label': item.get('dictLabel'), 'value': item.get('dictValue')} for item in job_group_list]
job_group_option_dict = {item.get('value'): item for item in job_group_option}
job_executor_list = await DictDataService.query_dict_data_list_from_cache_services(
request.app.state.redis, dict_type='sys_job_executor'
)
job_executor_option = [
{'label': item.get('dictLabel'), 'value': item.get('dictValue')} for item in job_executor_list
]
job_executor_option_dict = {item.get('value'): item for item in job_executor_option}
for item in job_log_list:
if item.get('status') == '0':
item['status'] = '正常'
else:
item['status'] = '暂停'
if str(item.get('jobGroup')) in job_group_option_dict:
item['jobGroup'] = job_group_option_dict.get(str(item.get('jobGroup'))).get('label')
if str(item.get('jobExecutor')) in job_executor_option_dict:
item['jobExecutor'] = job_executor_option_dict.get(str(item.get('jobExecutor'))).get('label')
binary_data = ExcelUtil.export_list2excel(job_log_list, mapping_dict)
return binary_data
@@ -0,0 +1,273 @@
from typing import Any
from fastapi import Request
from sqlalchemy.ext.asyncio import AsyncSession
from common.constant import CommonConstant, JobConstant
from common.vo import CrudResponseModel, PageModel
from config.get_scheduler import SchedulerUtil
from exceptions.exception import ServiceException
from module_admin.dao.job_dao import JobDao
from module_admin.entity.vo.job_vo import DeleteJobModel, EditJobModel, JobModel, JobPageQueryModel
from module_admin.service.dict_service import DictDataService
from utils.common_util import CamelCaseUtil
from utils.cron_util import CronUtil
from utils.excel_util import ExcelUtil
from utils.string_util import StringUtil
class JobService:
"""
定时任务管理模块服务层
"""
@classmethod
async def get_job_list_services(
cls, query_db: AsyncSession, query_object: JobPageQueryModel, is_page: bool = False
) -> PageModel | list[dict[str, Any]]:
"""
获取定时任务列表信息service
:param query_db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 定时任务列表信息对象
"""
job_list_result = await JobDao.get_job_list(query_db, query_object, is_page)
return job_list_result
@classmethod
async def check_job_unique_services(cls, query_db: AsyncSession, page_object: JobModel) -> bool:
"""
校验定时任务是否存在service
:param query_db: orm对象
:param page_object: 定时任务对象
:return: 校验结果
"""
job_id = -1 if page_object.job_id is None else page_object.job_id
job = await JobDao.get_job_detail_by_info(query_db, page_object)
if job and job.job_id != job_id:
return CommonConstant.NOT_UNIQUE
return CommonConstant.UNIQUE
@classmethod
async def add_job_services(cls, query_db: AsyncSession, page_object: JobModel) -> CrudResponseModel:
"""
新增定时任务信息service
:param query_db: orm对象
:param page_object: 新增定时任务对象
:return: 新增定时任务校验结果
"""
if not CronUtil.validate_cron_expression(page_object.cron_expression):
raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,Cron表达式不正确')
if StringUtil.contains_ignore_case(page_object.invoke_target, CommonConstant.LOOKUP_RMI):
raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不允许rmi调用')
if StringUtil.contains_any_ignore_case(
page_object.invoke_target, [CommonConstant.LOOKUP_LDAP, CommonConstant.LOOKUP_LDAPS]
):
raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不允许ldap(s)调用')
if StringUtil.contains_any_ignore_case(page_object.invoke_target, [CommonConstant.HTTP, CommonConstant.HTTPS]):
raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不允许http(s)调用')
if StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_ERROR_LIST):
raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串存在违规')
if not StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_WHITE_LIST):
raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不在白名单内')
if not await cls.check_job_unique_services(query_db, page_object):
raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,定时任务已存在')
try:
add_job = await JobDao.add_job_dao(query_db, page_object)
job_info = await cls.job_detail_services(query_db, add_job.job_id)
if job_info.status == '0':
SchedulerUtil.add_scheduler_job(job_info=job_info)
await query_db.commit()
await SchedulerUtil.request_scheduler_sync()
result = {'is_success': True, 'message': '新增成功'}
except Exception as e:
await query_db.rollback()
raise e
return CrudResponseModel(**result)
@classmethod
def _deal_edit_job(cls, page_object: EditJobModel, edit_job: dict[str, Any]) -> None:
"""
处理编辑定时任务字典
:param page_object: 编辑定时任务对象
:param edit_job: 编辑定时任务字典
"""
if page_object.type == 'status':
del edit_job['type']
@classmethod
async def edit_job_services(cls, query_db: AsyncSession, page_object: EditJobModel) -> CrudResponseModel:
"""
编辑定时任务信息service
:param query_db: orm对象
:param page_object: 编辑定时任务对象
:return: 编辑定时任务校验结果
"""
edit_job = page_object.model_dump(exclude_unset=True)
cls._deal_edit_job(page_object, edit_job)
job_info = await cls.job_detail_services(query_db, page_object.job_id)
if job_info:
if page_object.type != 'status':
if not CronUtil.validate_cron_expression(page_object.cron_expression):
raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,Cron表达式不正确')
if StringUtil.contains_ignore_case(page_object.invoke_target, CommonConstant.LOOKUP_RMI):
raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,目标字符串不允许rmi调用')
if StringUtil.contains_any_ignore_case(
page_object.invoke_target, [CommonConstant.LOOKUP_LDAP, CommonConstant.LOOKUP_LDAPS]
):
raise ServiceException(
message=f'修改定时任务{page_object.job_name}失败,目标字符串不允许ldap(s)调用'
)
if StringUtil.contains_any_ignore_case(
page_object.invoke_target, [CommonConstant.HTTP, CommonConstant.HTTPS]
):
raise ServiceException(
message=f'修改定时任务{page_object.job_name}失败,目标字符串不允许http(s)调用'
)
if StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_ERROR_LIST):
raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,目标字符串存在违规')
if not StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_WHITE_LIST):
raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,目标字符串不在白名单内')
if not await cls.check_job_unique_services(query_db, page_object):
raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,定时任务已存在')
try:
await JobDao.edit_job_dao(query_db, edit_job, job_info)
SchedulerUtil.remove_scheduler_job(job_id=edit_job.get('job_id'))
if edit_job.get('status') == '0':
job_info = await cls.job_detail_services(query_db, edit_job.get('job_id'))
SchedulerUtil.add_scheduler_job(job_info=job_info)
await query_db.commit()
await SchedulerUtil.request_scheduler_sync()
return CrudResponseModel(is_success=True, message='更新成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='定时任务不存在')
@classmethod
async def execute_job_once_services(cls, query_db: AsyncSession, page_object: JobModel) -> CrudResponseModel:
"""
执行一次定时任务service
:param query_db: orm对象
:param page_object: 定时任务对象
:return: 执行一次定时任务结果
"""
SchedulerUtil.remove_scheduler_job(job_id=page_object.job_id)
job_info = await cls.job_detail_services(query_db, page_object.job_id)
if job_info:
SchedulerUtil.execute_scheduler_job_once(job_info=job_info)
return CrudResponseModel(is_success=True, message='执行成功')
raise ServiceException(message='定时任务不存在')
@classmethod
async def delete_job_services(cls, query_db: AsyncSession, page_object: DeleteJobModel) -> CrudResponseModel:
"""
删除定时任务信息service
:param query_db: orm对象
:param page_object: 删除定时任务对象
:return: 删除定时任务校验结果
"""
if page_object.job_ids:
job_id_list = page_object.job_ids.split(',')
try:
for job_id in job_id_list:
await JobDao.delete_job_dao(query_db, JobModel(jobId=job_id))
SchedulerUtil.remove_scheduler_job(job_id=job_id)
await query_db.commit()
await SchedulerUtil.request_scheduler_sync()
return CrudResponseModel(is_success=True, message='删除成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='传入定时任务id为空')
@classmethod
async def job_detail_services(cls, query_db: AsyncSession, job_id: int) -> JobModel:
"""
获取定时任务详细信息service
:param query_db: orm对象
:param job_id: 定时任务id
:return: 定时任务id对应的信息
"""
job = await JobDao.get_job_detail_by_id(query_db, job_id=job_id)
result = JobModel(**CamelCaseUtil.transform_result(job)) if job else JobModel()
return result
@staticmethod
async def export_job_list_services(request: Request, job_list: list) -> bytes:
"""
导出定时任务信息service
:param request: Request对象
:param job_list: 定时任务信息列表
:return: 定时任务信息对应excel的二进制数据
"""
# 创建一个映射字典,将英文键映射到中文键
mapping_dict = {
'jobId': '任务编码',
'jobName': '任务名称',
'jobGroup': '任务组名',
'jobExecutor': '任务执行器',
'invokeTarget': '调用目标字符串',
'jobArgs': '位置参数',
'jobKwargs': '关键字参数',
'cronExpression': 'cron执行表达式',
'misfirePolicy': '计划执行错误策略',
'concurrent': '是否并发执行',
'status': '状态',
'createBy': '创建者',
'createTime': '创建时间',
'updateBy': '更新者',
'updateTime': '更新时间',
'remark': '备注',
}
job_group_list = await DictDataService.query_dict_data_list_from_cache_services(
request.app.state.redis, dict_type='sys_job_group'
)
job_group_option = [{'label': item.get('dictLabel'), 'value': item.get('dictValue')} for item in job_group_list]
job_group_option_dict = {item.get('value'): item for item in job_group_option}
job_executor_list = await DictDataService.query_dict_data_list_from_cache_services(
request.app.state.redis, dict_type='sys_job_executor'
)
job_executor_option = [
{'label': item.get('dictLabel'), 'value': item.get('dictValue')} for item in job_executor_list
]
job_executor_option_dict = {item.get('value'): item for item in job_executor_option}
for item in job_list:
if item.get('status') == '0':
item['status'] = '正常'
else:
item['status'] = '暂停'
if str(item.get('jobGroup')) in job_group_option_dict:
item['jobGroup'] = job_group_option_dict.get(str(item.get('jobGroup'))).get('label')
if str(item.get('jobExecutor')) in job_executor_option_dict:
item['jobExecutor'] = job_executor_option_dict.get(str(item.get('jobExecutor'))).get('label')
if item.get('misfirePolicy') == '1':
item['misfirePolicy'] = '立即执行'
elif item.get('misfirePolicy') == '2':
item['misfirePolicy'] = '执行一次'
else:
item['misfirePolicy'] = '放弃执行'
if item.get('concurrent') == '0':
item['concurrent'] = '允许'
else:
item['concurrent'] = '禁止'
binary_data = ExcelUtil.export_list2excel(job_list, mapping_dict)
return binary_data
@@ -0,0 +1,508 @@
import asyncio
import hashlib
import json
import os
import uuid
from typing import Any
from fastapi import Request
from redis import asyncio as aioredis
from sqlalchemy.ext.asyncio import AsyncSession
from common.vo import CrudResponseModel, PageModel
from config.database import AsyncSessionLocal
from config.env import LogConfig
from exceptions.exception import ServiceException
from middlewares.trace_middleware.ctx import TraceCtx
from module_admin.dao.log_dao import LoginLogDao, OperationLogDao
from module_admin.entity.vo.log_vo import (
DeleteLoginLogModel,
DeleteOperLogModel,
LogininforModel,
LoginLogPageQueryModel,
OperLogModel,
OperLogPageQueryModel,
UnlockUser,
)
from module_admin.service.dict_service import DictDataService
from utils.excel_util import ExcelUtil
from utils.log_util import LogSanitizer, logger
class OperationLogService:
"""
操作日志管理模块服务层
"""
@classmethod
async def get_operation_log_list_services(
cls, query_db: AsyncSession, query_object: OperLogPageQueryModel, is_page: bool = False
) -> PageModel | list[dict[str, Any]]:
"""
获取操作日志列表信息service
:param query_db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 操作日志列表信息对象
"""
operation_log_list_result = await OperationLogDao.get_operation_log_list(query_db, query_object, is_page)
return operation_log_list_result
@classmethod
async def add_operation_log_services(cls, query_db: AsyncSession, page_object: OperLogModel) -> CrudResponseModel:
"""
新增操作日志service
:param query_db: orm对象
:param page_object: 新增操作日志对象
:return: 新增操作日志校验结果
"""
try:
await OperationLogDao.add_operation_log_dao(query_db, page_object)
await query_db.commit()
return CrudResponseModel(is_success=True, message='新增成功')
except Exception as e:
await query_db.rollback()
raise e
@classmethod
async def delete_operation_log_services(
cls, query_db: AsyncSession, page_object: DeleteOperLogModel
) -> CrudResponseModel:
"""
删除操作日志信息service
:param query_db: orm对象
:param page_object: 删除操作日志对象
:return: 删除操作日志校验结果
"""
if page_object.oper_ids:
oper_id_list = page_object.oper_ids.split(',')
try:
for oper_id in oper_id_list:
await OperationLogDao.delete_operation_log_dao(query_db, OperLogModel(operId=oper_id))
await query_db.commit()
return CrudResponseModel(is_success=True, message='删除成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='传入操作日志id为空')
@classmethod
async def clear_operation_log_services(cls, query_db: AsyncSession) -> CrudResponseModel:
"""
清除操作日志信息service
:param query_db: orm对象
:return: 清除操作日志校验结果
"""
try:
await OperationLogDao.clear_operation_log_dao(query_db)
await query_db.commit()
return CrudResponseModel(is_success=True, message='清除成功')
except Exception as e:
await query_db.rollback()
raise e
@classmethod
async def export_operation_log_list_services(cls, request: Request, operation_log_list: list) -> bytes:
"""
导出操作日志信息service
:param request: Request对象
:param operation_log_list: 操作日志信息列表
:return: 操作日志信息对应excel的二进制数据
"""
# 创建一个映射字典,将英文键映射到中文键
mapping_dict = {
'operId': '日志编号',
'title': '系统模块',
'businessType': '操作类型',
'method': '方法名称',
'requestMethod': '请求方式',
'operName': '操作人员',
'deptName': '部门名称',
'operUrl': '请求URL',
'operIp': '操作地址',
'operLocation': '操作地点',
'operParam': '请求参数',
'jsonResult': '返回参数',
'status': '操作状态',
'error_msg': '错误消息',
'operTime': '操作日期',
'costTime': '消耗时间(毫秒)',
}
operation_type_list = await DictDataService.query_dict_data_list_from_cache_services(
request.app.state.redis, dict_type='sys_oper_type'
)
operation_type_option = [
{'label': item.get('dictLabel'), 'value': item.get('dictValue')} for item in operation_type_list
]
operation_type_option_dict = {item.get('value'): item for item in operation_type_option}
for item in operation_log_list:
if item.get('status') == 0:
item['status'] = '成功'
else:
item['status'] = '失败'
if str(item.get('businessType')) in operation_type_option_dict:
item['businessType'] = operation_type_option_dict.get(str(item.get('businessType'))).get('label')
binary_data = ExcelUtil.export_list2excel(operation_log_list, mapping_dict)
return binary_data
class LoginLogService:
"""
登录日志管理模块服务层
"""
@classmethod
async def get_login_log_list_services(
cls, query_db: AsyncSession, query_object: LoginLogPageQueryModel, is_page: bool = False
) -> PageModel | list[dict[str, Any]]:
"""
获取登录日志列表信息service
:param query_db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 登录日志列表信息对象
"""
operation_log_list_result = await LoginLogDao.get_login_log_list(query_db, query_object, is_page)
return operation_log_list_result
@classmethod
async def add_login_log_services(cls, query_db: AsyncSession, page_object: LogininforModel) -> CrudResponseModel:
"""
新增登录日志service
:param query_db: orm对象
:param page_object: 新增登录日志对象
:return: 新增登录日志校验结果
"""
try:
await LoginLogDao.add_login_log_dao(query_db, page_object)
await query_db.commit()
return CrudResponseModel(is_success=True, message='新增成功')
except Exception as e:
await query_db.rollback()
raise e
@classmethod
async def delete_login_log_services(
cls, query_db: AsyncSession, page_object: DeleteLoginLogModel
) -> CrudResponseModel:
"""
删除操作日志信息service
:param query_db: orm对象
:param page_object: 删除操作日志对象
:return: 删除操作日志校验结果
"""
if page_object.info_ids:
info_id_list = page_object.info_ids.split(',')
try:
for info_id in info_id_list:
await LoginLogDao.delete_login_log_dao(query_db, LogininforModel(infoId=info_id))
await query_db.commit()
return CrudResponseModel(is_success=True, message='删除成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='传入登录日志id为空')
@classmethod
async def clear_login_log_services(cls, query_db: AsyncSession) -> CrudResponseModel:
"""
清除操作日志信息service
:param query_db: orm对象
:return: 清除操作日志校验结果
"""
try:
await LoginLogDao.clear_login_log_dao(query_db)
await query_db.commit()
return CrudResponseModel(is_success=True, message='清除成功')
except Exception as e:
await query_db.rollback()
raise e
@classmethod
async def unlock_user_services(cls, request: Request, unlock_user: UnlockUser) -> CrudResponseModel:
locked_user = await request.app.state.redis.get(f'account_lock:{unlock_user.user_name}')
if locked_user:
await request.app.state.redis.delete(f'account_lock:{unlock_user.user_name}')
return CrudResponseModel(is_success=True, message='解锁成功')
raise ServiceException(message='该用户未锁定')
@staticmethod
async def export_login_log_list_services(login_log_list: list) -> bytes:
"""
导出登录日志信息service
:param login_log_list: 登录日志信息列表
:return: 登录日志信息对应excel的二进制数据
"""
# 创建一个映射字典,将英文键映射到中文键
mapping_dict = {
'infoId': '访问编号',
'userName': '用户名称',
'ipaddr': '登录地址',
'loginLocation': '登录地点',
'browser': '浏览器',
'os': '操作系统',
'status': '登录状态',
'msg': '操作信息',
'loginTime': '登录日期',
}
for item in login_log_list:
if item.get('status') == '0':
item['status'] = '成功'
else:
item['status'] = '失败'
binary_data = ExcelUtil.export_list2excel(login_log_list, mapping_dict)
return binary_data
class LogQueueService:
"""
日志队列服务
"""
@classmethod
def _build_event_id(cls, request_id: str, log_type: str, source: str) -> str:
"""
生成日志事件唯一标识
:param request_id: 请求唯一标识
:param log_type: 日志类型
:param source: 日志来源
:return: 事件唯一标识
"""
if not request_id:
return uuid.uuid4().hex
base = f'{request_id}:{log_type}:{source}'
return hashlib.md5(base.encode('utf-8')).hexdigest()
@classmethod
async def _xadd_event(cls, redis: aioredis.Redis, event_type: str, payload: dict, source: str) -> None:
"""
写入日志事件到Redis Streams
:param redis: Redis连接对象
:param event_type: 事件类型
:param payload: 事件负载
:param source: 日志来源
:return: None
"""
request_id = TraceCtx.get_request_id()
trace_id = TraceCtx.get_trace_id()
span_id = TraceCtx.get_span_id()
event_id = cls._build_event_id(request_id, event_type, source)
await redis.xadd(
LogConfig.log_stream_key,
{
'event_id': event_id,
'event_type': event_type,
'request_id': request_id,
'trace_id': trace_id,
'span_id': span_id,
'payload': json.dumps(payload, ensure_ascii=False, default=str),
},
maxlen=LogConfig.log_stream_maxlen,
approximate=True,
)
@classmethod
async def enqueue_login_log(cls, request: Request, login_log: LogininforModel, source: str) -> None:
"""
登录日志入队
:param request: Request对象
:param login_log: 登录日志模型
:param source: 日志来源
:return: None
"""
payload = LogSanitizer.sanitize_data(login_log.model_dump(by_alias=True, exclude_none=True))
await cls._xadd_event(request.app.state.redis, 'login', payload, source)
@classmethod
async def enqueue_operation_log(cls, request: Request, operation_log: OperLogModel, source: str) -> None:
"""
操作日志入队
:param request: Request对象
:param operation_log: 操作日志模型
:param source: 日志来源
:return: None
"""
payload = LogSanitizer.sanitize_data(operation_log.model_dump(by_alias=True, exclude_none=True))
await cls._xadd_event(request.app.state.redis, 'operation', payload, source)
class LogAggregatorService:
"""
日志聚合消费服务
"""
@classmethod
async def _ensure_group(cls, redis: aioredis.Redis) -> None:
"""
初始化消费组
:param redis: Redis连接对象
:return: None
"""
try:
await redis.xgroup_create(
name=LogConfig.log_stream_key,
groupname=LogConfig.log_stream_group,
id='0-0',
mkstream=True,
)
except Exception as exc:
if 'BUSYGROUP' not in str(exc):
raise
@classmethod
async def _acquire_dedup(cls, redis: aioredis.Redis, event_id: str) -> bool:
"""
获取去重锁
:param redis: Redis连接对象
:param event_id: 事件唯一标识
:return: 是否获取成功
"""
if not event_id:
return False
key = f'{LogConfig.log_stream_dedup_prefix}:{event_id}'
return await redis.set(key, '1', nx=True, ex=LogConfig.log_stream_dedup_ttl)
@classmethod
async def _release_dedup(cls, redis: aioredis.Redis, event_id: str) -> None:
"""
释放去重锁
:param redis: Redis连接对象
:param event_id: 事件唯一标识
:return: None
"""
if not event_id:
return
await redis.delete(f'{LogConfig.log_stream_dedup_prefix}:{event_id}')
@classmethod
async def _claim_pending(cls, redis: aioredis.Redis, consumer_name: str) -> None:
"""
认领并处理超时未确认的消息
:param redis: Redis连接对象
:param consumer_name: 消费者名称
:return: None
"""
if LogConfig.log_stream_claim_idle_ms <= 0:
return
start_id = '0-0'
while True:
result = await redis.xautoclaim(
name=LogConfig.log_stream_key,
groupname=LogConfig.log_stream_group,
consumername=consumer_name,
min_idle_time=LogConfig.log_stream_claim_idle_ms,
start_id=start_id,
count=LogConfig.log_stream_claim_batch_size,
)
if not result:
return
next_start_id, messages = result[0], result[1]
if messages:
await cls._process_messages(redis, LogConfig.log_stream_key, messages)
if not messages or next_start_id == start_id:
return
start_id = next_start_id
@classmethod
async def consume_stream(cls, redis: aioredis.Redis) -> None:
"""
消费日志队列
:param redis: Redis连接对象
:return: None
"""
await cls._ensure_group(redis)
consumer_name = f'{LogConfig.log_stream_consumer_prefix}-{os.getpid()}-{uuid.uuid4().hex[:6]}'
last_claim_time = 0.0
while True:
try:
now = asyncio.get_running_loop().time()
if now - last_claim_time >= LogConfig.log_stream_claim_interval_ms / 1000:
await cls._claim_pending(redis, consumer_name)
last_claim_time = now
result = await redis.xreadgroup(
groupname=LogConfig.log_stream_group,
consumername=consumer_name,
streams={LogConfig.log_stream_key: '>'},
count=LogConfig.log_stream_batch_size,
block=LogConfig.log_stream_block_ms,
)
if not result:
continue
for stream_name, messages in result:
await cls._process_messages(redis, stream_name, messages)
except asyncio.CancelledError:
raise
except Exception as exc:
logger.error(f'日志聚合消费异常: {exc}')
await asyncio.sleep(1)
@classmethod
async def _process_messages(cls, redis: aioredis.Redis, stream_name: str, messages: list[tuple[str, dict]]) -> None:
"""
处理消息并落库
:param redis: Redis连接对象
:param stream_name: Stream名称
:param messages: 消息列表
:return: None
"""
if not messages:
return
async with AsyncSessionLocal() as session:
ack_ids: list[str] = []
dedup_event_ids: list[str] = []
try:
for message_id, data in messages:
event_type = data.get('event_type')
event_id = data.get('event_id')
payload_raw = data.get('payload') or '{}'
if event_type not in {'login', 'operation'}:
ack_ids.append(message_id)
continue
acquired = await cls._acquire_dedup(redis, event_id)
if not acquired:
ack_ids.append(message_id)
continue
dedup_event_ids.append(event_id)
payload = json.loads(payload_raw)
if event_type == 'login':
await LoginLogDao.add_login_log_dao(session, LogininforModel(**payload))
elif event_type == 'operation':
await OperationLogDao.add_operation_log_dao(session, OperLogModel(**payload))
ack_ids.append(message_id)
if ack_ids:
await session.commit()
await redis.xack(stream_name, LogConfig.log_stream_group, *ack_ids)
except Exception:
await session.rollback()
for event_id in dedup_event_ids:
await cls._release_dedup(redis, event_id)
raise
@@ -0,0 +1,656 @@
import random
import uuid
from datetime import datetime, timedelta, timezone
from typing import Any
import jwt
from fastapi import Depends, Form, Request
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jwt.exceptions import InvalidTokenError
from sqlalchemy import Row
from sqlalchemy.ext.asyncio import AsyncSession
from common.constant import CommonConstant, MenuConstant
from common.context import RequestContext
from common.enums import RedisInitKeyConfig
from common.vo import CrudResponseModel
from config.env import AppConfig, JwtConfig
from config.get_db import get_db
from exceptions.exception import AuthException, LoginException, ServiceException
from module_admin.dao.login_dao import login_by_account
from module_admin.dao.user_dao import UserDao
from module_admin.entity.do.dept_do import SysDept
from module_admin.entity.do.menu_do import SysMenu
from module_admin.entity.do.user_do import SysUser
from module_admin.entity.vo.login_vo import MenuTreeModel, MetaModel, RouterModel, SmsCode, UserLogin, UserRegister
from module_admin.entity.vo.user_vo import AddUserModel, CurrentUserModel, ResetUserModel, TokenData, UserInfoModel
from module_admin.service.user_service import UserService
from utils.client_ip_util import ClientIPUtil
from utils.common_util import CamelCaseUtil
from utils.log_util import logger
from utils.message_util import message_service
from utils.pwd_util import PwdUtil
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='login')
class CustomOAuth2PasswordRequestForm(OAuth2PasswordRequestForm):
"""
自定义OAuth2PasswordRequestForm类,增加验证码及会话编号参数
"""
def __init__(
self,
grant_type: str = Form(default=None, pattern='password'),
username: str = Form(),
password: str = Form(),
scope: str = Form(default=''),
client_id: str | None = Form(default=None),
client_secret: str | None = Form(default=None),
code: str | None = Form(default=''),
uuid: str | None = Form(default=''),
login_info: dict[str, str] | None = Form(default=None),
) -> None:
super().__init__(
grant_type=grant_type,
username=username,
password=password,
scope=scope,
client_id=client_id,
client_secret=client_secret,
)
self.code = code
self.uuid = uuid
self.login_info = login_info
class LoginService:
"""
登录模块服务层
"""
@classmethod
async def authenticate_user(
cls, request: Request, query_db: AsyncSession, login_user: UserLogin
) -> Row[tuple[SysUser, SysDept]]:
"""
根据用户名密码校验用户登录
:param request: Request对象
:param query_db: orm对象
:param login_user: 登录用户对象
:return: 校验结果
"""
await cls.__check_login_ip(request)
account_lock = await request.app.state.redis.get(
f'{RedisInitKeyConfig.ACCOUNT_LOCK.key}:{login_user.user_name}'
)
if login_user.user_name == account_lock:
logger.warning('账号已锁定,请稍后再试')
raise LoginException(data='', message='账号已锁定,请稍后再试')
# 判断请求是否来自于api文档,如果是返回指定格式的结果,用于修复api文档认证成功后token显示undefined的bug
request_from_swagger = (
request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False
)
request_from_redoc = (
request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False
)
# 判断是否开启验证码,开启则验证,否则不验证(dev模式下来自API文档的登录请求不检验)
if not login_user.captcha_enabled or (
(request_from_swagger or request_from_redoc) and AppConfig.app_env == 'dev'
):
pass
else:
await cls.__check_login_captcha(request, login_user)
user = await login_by_account(query_db, login_user.user_name)
if not user:
logger.warning('用户不存在')
raise LoginException(data='', message='用户不存在')
if not PwdUtil.verify_password(login_user.password, user[0].password):
cache_password_error_count = await request.app.state.redis.get(
f'{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.key}:{login_user.user_name}'
)
password_error_counted = 0
if cache_password_error_count:
password_error_counted = cache_password_error_count
password_error_count = int(password_error_counted) + 1
await request.app.state.redis.set(
f'{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.key}:{login_user.user_name}',
password_error_count,
ex=timedelta(minutes=10),
)
if password_error_count > CommonConstant.PASSWORD_ERROR_COUNT:
await request.app.state.redis.delete(
f'{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.key}:{login_user.user_name}'
)
await request.app.state.redis.set(
f'{RedisInitKeyConfig.ACCOUNT_LOCK.key}:{login_user.user_name}',
login_user.user_name,
ex=timedelta(minutes=10),
)
logger.warning('10分钟内密码已输错超过5次,账号已锁定,请10分钟后再试')
raise LoginException(data='', message='10分钟内密码已输错超过5次,账号已锁定,请10分钟后再试')
logger.warning('密码错误')
raise LoginException(data='', message='密码错误')
if user[0].status == '1':
logger.warning('用户已停用')
raise LoginException(data='', message='用户已停用')
await request.app.state.redis.delete(f'{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.key}:{login_user.user_name}')
return user
@classmethod
async def __check_login_ip(cls, request: Request) -> bool:
"""
校验用户登录ip是否在黑名单内
:param request: Request对象
:return: 校验结果
"""
black_ip_value = await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.login.blackIPList')
black_ip_list = black_ip_value.split(',') if black_ip_value else []
if ClientIPUtil.get_client_ip(request) in black_ip_list:
logger.warning('当前IP禁止登录')
raise LoginException(data='', message='当前IP禁止登录')
return True
@classmethod
async def __check_login_captcha(cls, request: Request, login_user: UserLogin) -> bool:
"""
校验用户登录验证码
:param request: Request对象
:param login_user: 登录用户对象
:return: 校验结果
"""
captcha_value = await request.app.state.redis.get(f'{RedisInitKeyConfig.CAPTCHA_CODES.key}:{login_user.uuid}')
if not captcha_value:
logger.warning('验证码已失效')
raise LoginException(data='', message='验证码已失效')
if login_user.code != str(captcha_value):
logger.warning('验证码错误')
raise LoginException(data='', message='验证码错误')
return True
@classmethod
async def create_access_token(cls, data: dict, expires_delta: timedelta | None = None) -> str:
"""
根据登录信息创建当前用户token
:param data: 登录信息
:param expires_delta: token有效期
:return: token
"""
to_encode = data.copy()
if expires_delta:
expire = datetime.now(timezone.utc) + expires_delta
else:
expire = datetime.now(timezone.utc) + timedelta(minutes=30)
to_encode.update({'exp': expire})
encoded_jwt = jwt.encode(to_encode, JwtConfig.jwt_secret_key, algorithm=JwtConfig.jwt_algorithm)
return encoded_jwt
@classmethod
async def get_current_user(
cls, request: Request = Request, token: str = Depends(oauth2_scheme), query_db: AsyncSession = Depends(get_db)
) -> CurrentUserModel:
"""
根据token获取当前用户信息
:param request: Request对象
:param token: 用户token
:param query_db: orm对象
:return: 当前用户信息对象
:raise: 令牌异常AuthException
"""
# if token[:6] != 'Bearer':
# logger.warning("用户token不合法")
# raise AuthException(data="", message="用户token不合法")
try:
if token.startswith('Bearer'):
token = token.split(' ')[1]
payload = jwt.decode(token, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm])
user_id: str = payload.get('user_id')
session_id: str = payload.get('session_id')
if not user_id:
logger.warning('用户token不合法')
raise AuthException(data='', message='用户token不合法')
token_data = TokenData(user_id=int(user_id))
except InvalidTokenError as e:
logger.warning('用户token已失效,请重新登录')
raise AuthException(data='', message='用户token已失效,请重新登录') from e
query_user = await UserDao.get_user_by_id(query_db, user_id=token_data.user_id)
if query_user.get('user_basic_info') is None:
logger.warning('用户token不合法')
raise AuthException(data='', message='用户token不合法')
if AppConfig.app_same_time_login:
redis_token = await request.app.state.redis.get(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}')
else:
# 此方法可实现同一账号同一时间只能登录一次
redis_token = await request.app.state.redis.get(
f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{query_user.get("user_basic_info").user_id}'
)
if token == redis_token:
if AppConfig.app_same_time_login:
await request.app.state.redis.set(
f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}',
redis_token,
ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes),
)
else:
await request.app.state.redis.set(
f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{query_user.get("user_basic_info").user_id}',
redis_token,
ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes),
)
role_id_list = [item.role_id for item in query_user.get('user_role_info')]
if 1 in role_id_list: # noqa: SIM108
permissions = ['*:*:*']
else:
permissions = [row.perms for row in query_user.get('user_menu_info')]
post_ids = ','.join([str(row.post_id) for row in query_user.get('user_post_info')])
role_ids = ','.join([str(row.role_id) for row in query_user.get('user_role_info')])
roles = [row.role_key for row in query_user.get('user_role_info')]
is_default_modify_pwd = await cls.__init_password_is_modify(
request, query_user.get('user_basic_info').pwd_update_date
)
is_password_expired = await cls.__password_is_expired(
request, query_user.get('user_basic_info').pwd_update_date
)
current_user = CurrentUserModel(
permissions=permissions,
roles=roles,
user=UserInfoModel(
**CamelCaseUtil.transform_result(query_user.get('user_basic_info')),
postIds=post_ids,
roleIds=role_ids,
dept=CamelCaseUtil.transform_result(query_user.get('user_dept_info')),
role=CamelCaseUtil.transform_result(query_user.get('user_role_info')),
),
isDefaultModifyPwd=is_default_modify_pwd,
isPasswordExpired=is_password_expired,
)
# 设置当前用户信息到上下文
RequestContext.set_current_user(current_user)
return current_user
logger.warning('用户token已失效,请重新登录')
raise AuthException(data='', message='用户token已失效,请重新登录')
@classmethod
async def __init_password_is_modify(cls, request: Request, pwd_update_date: datetime) -> bool:
"""
判断当前用户是否初始密码登录
:param request: Request对象
:param pwd_update_date: 密码最后更新时间
:return: 是否初始密码登录
"""
init_password_is_modify = await request.app.state.redis.get(
f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.initPasswordModify'
)
return init_password_is_modify == '1' and pwd_update_date is None
@classmethod
async def __password_is_expired(cls, request: Request, pwd_update_date: datetime) -> bool:
"""
判断当前用户密码是否过期
:param request: Request对象
:param pwd_update_date: 密码最后更新时间
:return: 密码是否过期
"""
password_validate_days = await request.app.state.redis.get(
f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.passwordValidateDays'
)
if password_validate_days and int(password_validate_days) > 0:
if pwd_update_date is None:
return True
expire_date = pwd_update_date + timedelta(days=int(password_validate_days))
if datetime.now() > expire_date:
return True
return False
@classmethod
async def get_current_user_routers(cls, user_id: int, query_db: AsyncSession) -> list[dict[str, Any]]:
"""
根据用户id获取当前用户路由信息
:param user_id: 用户id
:param query_db: orm对象
:return: 当前用户路由信息对象
"""
query_user = await UserDao.get_user_by_id(query_db, user_id=user_id)
user_router_menu = sorted(
[
row
for row in query_user.get('user_menu_info')
if row.menu_type in [MenuConstant.TYPE_DIR, MenuConstant.TYPE_MENU]
],
key=lambda x: x.order_num,
)
menus = cls.__generate_menus(0, user_router_menu)
user_router = cls.__generate_user_router_menu(menus)
return [router.model_dump(exclude_unset=True, by_alias=True) for router in user_router]
@classmethod
def __generate_menus(cls, pid: int, permission_list: list[SysMenu]) -> list[MenuTreeModel]:
"""
工具方法:根据菜单信息生成菜单信息树形嵌套数据
:param pid: 菜单id
:param permission_list: 菜单列表信息
:return: 菜单信息树形嵌套数据
"""
menu_list: list[MenuTreeModel] = []
for permission in permission_list:
if permission.parent_id == pid:
children = cls.__generate_menus(permission.menu_id, permission_list)
menu_list_data = MenuTreeModel(**CamelCaseUtil.transform_result(permission))
if children:
menu_list_data.children = children
menu_list.append(menu_list_data)
return menu_list
@classmethod
def __generate_user_router_menu(cls, permission_list: list[MenuTreeModel]) -> list[RouterModel]:
"""
工具方法:根据菜单树信息生成路由信息树形嵌套数据
:param permission_list: 菜单树列表信息
:return: 路由信息树形嵌套数据
"""
router_list: list[RouterModel] = []
for permission in permission_list:
router = RouterModel(
hidden=permission.visible == '1',
name=RouterUtil.get_router_name(permission),
path=RouterUtil.get_router_path(permission),
component=RouterUtil.get_component(permission),
query=permission.query,
meta=MetaModel(
title=permission.menu_name,
icon=permission.icon,
noCache=permission.is_cache == 1,
link=permission.path if RouterUtil.is_http(permission.path) else None,
),
)
c_menus = permission.children
if c_menus and permission.menu_type == MenuConstant.TYPE_DIR:
router.always_show = True
router.redirect = 'noRedirect'
router.children = cls.__generate_user_router_menu(c_menus)
elif RouterUtil.is_menu_frame(permission):
router.meta = None
children_list: list[RouterModel] = []
children = RouterModel(
path=permission.path,
component=permission.component,
name=RouterUtil.get_route_name(permission.route_name, permission.path),
meta=MetaModel(
title=permission.menu_name,
icon=permission.icon,
noCache=permission.is_cache == 1,
link=permission.path if RouterUtil.is_http(permission.path) else None,
),
query=permission.query,
)
children_list.append(children)
router.children = children_list
elif permission.parent_id == 0 and RouterUtil.is_inner_link(permission):
router.meta = MetaModel(title=permission.menu_name, icon=permission.icon)
router.path = '/'
children_list: list[RouterModel] = []
router_path = RouterUtil.inner_link_replace_each(permission.path)
children = RouterModel(
path=router_path,
component=MenuConstant.INNER_LINK,
name=RouterUtil.get_route_name(permission.route_name, permission.path),
meta=MetaModel(
title=permission.menu_name,
icon=permission.icon,
link=permission.path if RouterUtil.is_http(permission.path) else None,
),
)
children_list.append(children)
router.children = children_list
router_list.append(router)
return router_list
@classmethod
async def register_user_services(
cls, request: Request, query_db: AsyncSession, user_register: UserRegister
) -> CrudResponseModel:
"""
用户注册services
:param request: Request对象
:param query_db: orm对象
:param user_register: 注册用户对象
:return: 注册结果
"""
register_enabled = (
await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.registerUser') == 'true'
)
captcha_enabled = (
await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.captchaEnabled')
== 'true'
)
if user_register.password == user_register.confirm_password:
if register_enabled:
if captcha_enabled:
captcha_value = await request.app.state.redis.get(
f'{RedisInitKeyConfig.CAPTCHA_CODES.key}:{user_register.uuid}'
)
if not captcha_value:
raise ServiceException(message='验证码已失效')
if user_register.code != str(captcha_value):
raise ServiceException(message='验证码错误')
add_user = AddUserModel(
userName=user_register.username,
nickName=user_register.username,
password=PwdUtil.get_password_hash(user_register.password),
pwdUpdateDate=datetime.now(),
)
result = await UserService.add_user_services(query_db, add_user)
return result
raise ServiceException(message='注册程序已关闭,禁止注册')
raise ServiceException(message='两次输入的密码不一致')
@classmethod
async def get_sms_code_services(cls, request: Request, query_db: AsyncSession, user: ResetUserModel) -> SmsCode:
"""
获取短信验证码service
:param request: Request对象
:param query_db: orm对象
:param user: 用户对象
:return: 短信验证码对象
"""
redis_sms_result = await request.app.state.redis.get(f'{RedisInitKeyConfig.SMS_CODE.key}:{user.session_id}')
if redis_sms_result:
return SmsCode(is_success=False, sms_code='', session_id='', message='短信验证码仍在有效期内')
is_user = await UserDao.get_user_by_name(query_db, user.user_name)
if is_user:
sms_code = str(random.randint(100000, 999999))
session_id = str(uuid.uuid4())
await request.app.state.redis.set(
f'{RedisInitKeyConfig.SMS_CODE.key}:{session_id}', sms_code, ex=timedelta(minutes=2)
)
# 此处模拟调用短信服务
message_service(sms_code)
return SmsCode(is_success=True, sms_code=sms_code, session_id=session_id, message='获取成功')
return SmsCode(is_success=False, sms_code='', session_id='', message='用户不存在')
@classmethod
async def forget_user_services(
cls, request: Request, query_db: AsyncSession, forget_user: ResetUserModel
) -> CrudResponseModel:
"""
用户忘记密码services
:param request: Request对象
:param query_db: orm对象
:param forget_user: 重置用户对象
:return: 重置结果
"""
redis_sms_result = await request.app.state.redis.get(
f'{RedisInitKeyConfig.SMS_CODE.key}:{forget_user.session_id}'
)
if forget_user.sms_code == redis_sms_result:
forget_user.password = PwdUtil.get_password_hash(forget_user.password)
forget_user.user_id = (await UserDao.get_user_by_name(query_db, forget_user.user_name)).user_id
edit_result = await UserService.reset_user_services(query_db, forget_user)
result = edit_result.dict()
elif not redis_sms_result:
result = {'is_success': False, 'message': '短信验证码已过期'}
else:
await request.app.state.redis.delete(f'{RedisInitKeyConfig.SMS_CODE.key}:{forget_user.session_id}')
result = {'is_success': False, 'message': '短信验证码不正确'}
return CrudResponseModel(**result)
@classmethod
async def logout_services(cls, request: Request, token_id: str) -> bool:
"""
退出登录services
:param request: Request对象
:param token_id: 令牌编号
:return: 退出登录结果
"""
await request.app.state.redis.delete(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{token_id}')
# await request.app.state.redis.delete(f'{current_user.user.user_id}_access_token')
# await request.app.state.redis.delete(f'{current_user.user.user_id}_session_id')
return True
class RouterUtil:
"""
路由处理工具类
"""
@classmethod
def get_router_name(cls, menu: MenuTreeModel) -> str:
"""
获取路由名称
:param menu: 菜单数对象
:return: 路由名称
"""
# 非外链并且是一级目录(类型为目录)
if cls.is_menu_frame(menu):
return ''
return cls.get_route_name(menu.route_name, menu.path)
@classmethod
def get_route_name(cls, name: str, path: str) -> str:
"""
获取路由名称,如没有配置路由名称则取路由地址
:param name: 路由名称
:param path: 路由地址
:return: 路由名称(驼峰格式)
"""
router_name = name if name else path
return router_name.capitalize()
@classmethod
def get_router_path(cls, menu: MenuTreeModel) -> str | None:
"""
获取路由地址
:param menu: 菜单数对象
:return: 路由地址
"""
# 内链打开外网方式
router_path = menu.path
if menu.parent_id != 0 and cls.is_inner_link(menu):
router_path = cls.inner_link_replace_each(router_path)
# 非外链并且是一级目录(类型为目录)
if menu.parent_id == 0 and menu.menu_type == MenuConstant.TYPE_DIR and menu.is_frame == MenuConstant.NO_FRAME:
router_path = f'/{menu.path}'
# 非外链并且是一级目录(类型为菜单)
elif cls.is_menu_frame(menu):
router_path = '/'
return router_path
@classmethod
def get_component(cls, menu: MenuTreeModel) -> str:
"""
获取组件信息
:param menu: 菜单数对象
:return: 组件信息
"""
component = MenuConstant.LAYOUT
if menu.component and not cls.is_menu_frame(menu):
component = menu.component
elif (menu.component is None or menu.component == '') and menu.parent_id != 0 and cls.is_inner_link(menu):
component = MenuConstant.INNER_LINK
elif (menu.component is None or menu.component == '') and cls.is_parent_view(menu):
component = MenuConstant.PARENT_VIEW
return component
@classmethod
def is_menu_frame(cls, menu: MenuTreeModel) -> bool:
"""
判断是否为菜单内部跳转
:param menu: 菜单数对象
:return: 是否为菜单内部跳转
"""
return (
menu.parent_id == 0 and menu.menu_type == MenuConstant.TYPE_MENU and menu.is_frame == MenuConstant.NO_FRAME
)
@classmethod
def is_inner_link(cls, menu: MenuTreeModel) -> bool:
"""
判断是否为内链组件
:param menu: 菜单数对象
:return: 是否为内链组件
"""
return menu.is_frame == MenuConstant.NO_FRAME and cls.is_http(menu.path)
@classmethod
def is_parent_view(cls, menu: MenuTreeModel) -> bool:
"""
判断是否为parent_view组件
:param menu: 菜单数对象
:return: 是否为parent_view组件
"""
return menu.parent_id != 0 and menu.menu_type == MenuConstant.TYPE_DIR
@classmethod
def is_http(cls, link: str) -> bool:
"""
判断是否为http(s)://开头
:param link: 链接
:return: 是否为http(s)://开头
"""
return link.startswith((CommonConstant.HTTP, CommonConstant.HTTPS))
@classmethod
def inner_link_replace_each(cls, path: str) -> str:
"""
内链域名特殊字符替换
:param path: 内链域名
:return: 替换后的内链域名
"""
old_values = [CommonConstant.HTTP, CommonConstant.HTTPS, CommonConstant.WWW, '.', ':']
new_values = ['', '', '', '/', '/']
for old, new in zip(old_values, new_values, strict=False):
path = path.replace(old, new)
return path
@@ -0,0 +1,217 @@
from collections.abc import Sequence
from typing import Any
from sqlalchemy.ext.asyncio import AsyncSession
from common.constant import CommonConstant, MenuConstant
from common.vo import CrudResponseModel
from exceptions.exception import ServiceException, ServiceWarning
from module_admin.dao.menu_dao import MenuDao
from module_admin.dao.role_dao import RoleDao
from module_admin.entity.do.menu_do import SysMenu
from module_admin.entity.vo.menu_vo import DeleteMenuModel, MenuModel, MenuQueryModel, MenuTreeModel
from module_admin.entity.vo.role_vo import RoleMenuQueryModel
from module_admin.entity.vo.user_vo import CurrentUserModel
from utils.common_util import CamelCaseUtil
from utils.string_util import StringUtil
class MenuService:
"""
菜单管理模块服务层
"""
@classmethod
async def get_menu_tree_services(
cls, query_db: AsyncSession, current_user: CurrentUserModel | None = None
) -> list[dict[str, Any]]:
"""
获取菜单树信息service
:param query_db: orm对象
:param current_user: 当前用户对象
:return: 菜单树信息对象
"""
menu_list_result = await MenuDao.get_menu_list_for_tree(
query_db, current_user.user.user_id, current_user.user.role
)
menu_tree_model_result = cls.list_to_tree(menu_list_result)
menu_tree_result = [menu.model_dump(exclude_unset=True, by_alias=True) for menu in menu_tree_model_result]
return menu_tree_result
@classmethod
async def get_role_menu_tree_services(
cls, query_db: AsyncSession, role_id: int, current_user: CurrentUserModel | None = None
) -> RoleMenuQueryModel:
"""
根据角色id获取菜单树信息service
:param query_db: orm对象
:param role_id: 角色id
:param current_user: 当前用户对象
:return: 当前角色id的菜单树信息对象
"""
menu_list_result = await MenuDao.get_menu_list_for_tree(
query_db, current_user.user.user_id, current_user.user.role
)
menu_tree_result = cls.list_to_tree(menu_list_result)
role = await RoleDao.get_role_detail_by_id(query_db, role_id)
role_menu_list = await RoleDao.get_role_menu_dao(query_db, role)
checked_keys = [row.menu_id for row in role_menu_list]
result = RoleMenuQueryModel(menus=menu_tree_result, checkedKeys=checked_keys)
return result
@classmethod
async def get_menu_list_services(
cls, query_db: AsyncSession, page_object: MenuQueryModel, current_user: CurrentUserModel | None = None
) -> list[dict[str, Any]]:
"""
获取菜单列表信息service
:param query_db: orm对象
:param page_object: 分页查询参数对象
:param current_user: 当前用户对象
:return: 菜单列表信息对象
"""
menu_list_result = await MenuDao.get_menu_list(
query_db, page_object, current_user.user.user_id, current_user.user.role
)
return CamelCaseUtil.transform_result(menu_list_result)
@classmethod
async def check_menu_name_unique_services(cls, query_db: AsyncSession, page_object: MenuModel) -> bool:
"""
校验菜单名称是否唯一service
:param query_db: orm对象
:param page_object: 菜单对象
:return: 校验结果
"""
menu_id = -1 if page_object.menu_id is None else page_object.menu_id
menu = await MenuDao.get_menu_detail_by_info(query_db, MenuModel(menuName=page_object.menu_name))
if menu and menu.menu_id != menu_id:
return CommonConstant.NOT_UNIQUE
return CommonConstant.UNIQUE
@classmethod
async def add_menu_services(cls, query_db: AsyncSession, page_object: MenuModel) -> CrudResponseModel:
"""
新增菜单信息service
:param query_db: orm对象
:param page_object: 新增菜单对象
:return: 新增菜单校验结果
"""
if not await cls.check_menu_name_unique_services(query_db, page_object):
raise ServiceException(message=f'新增菜单{page_object.menu_name}失败,菜单名称已存在')
if page_object.is_frame == MenuConstant.YES_FRAME and not StringUtil.is_http(page_object.path):
raise ServiceException(message=f'新增菜单{page_object.menu_name}失败,地址必须以http(s)://开头')
try:
await MenuDao.add_menu_dao(query_db, page_object)
await query_db.commit()
return CrudResponseModel(is_success=True, message='新增成功')
except Exception as e:
await query_db.rollback()
raise e
@classmethod
async def edit_menu_services(cls, query_db: AsyncSession, page_object: MenuModel) -> CrudResponseModel:
"""
编辑菜单信息service
:param query_db: orm对象
:param page_object: 编辑部门对象
:return: 编辑菜单校验结果
"""
edit_menu = page_object.model_dump(exclude_unset=True)
menu_info = await cls.menu_detail_services(query_db, page_object.menu_id)
if menu_info.menu_id:
if not await cls.check_menu_name_unique_services(query_db, page_object):
raise ServiceException(message=f'修改菜单{page_object.menu_name}失败,菜单名称已存在')
if page_object.is_frame == MenuConstant.YES_FRAME and not StringUtil.is_http(page_object.path):
raise ServiceException(message=f'修改菜单{page_object.menu_name}失败,地址必须以http(s)://开头')
if page_object.menu_id == page_object.parent_id:
raise ServiceException(message=f'修改菜单{page_object.menu_name}失败,上级菜单不能选择自己')
try:
await MenuDao.edit_menu_dao(query_db, edit_menu)
await query_db.commit()
return CrudResponseModel(is_success=True, message='更新成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='菜单不存在')
@classmethod
async def delete_menu_services(cls, query_db: AsyncSession, page_object: DeleteMenuModel) -> CrudResponseModel:
"""
删除菜单信息service
:param query_db: orm对象
:param page_object: 删除菜单对象
:return: 删除菜单校验结果
"""
if page_object.menu_ids:
menu_id_list = page_object.menu_ids.split(',')
try:
for menu_id in menu_id_list:
if (await MenuDao.has_child_by_menu_id_dao(query_db, int(menu_id))) > 0:
raise ServiceWarning(message='存在子菜单,不允许删除')
if (await MenuDao.check_menu_exist_role_dao(query_db, int(menu_id))) > 0:
raise ServiceWarning(message='菜单已分配,不允许删除')
await MenuDao.delete_menu_dao(query_db, MenuModel(menuId=menu_id))
await query_db.commit()
return CrudResponseModel(is_success=True, message='删除成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='传入菜单id为空')
@classmethod
async def menu_detail_services(cls, query_db: AsyncSession, menu_id: int) -> MenuModel:
"""
获取菜单详细信息service
:param query_db: orm对象
:param menu_id: 菜单id
:return: 菜单id对应的信息
"""
menu = await MenuDao.get_menu_detail_by_id(query_db, menu_id=menu_id)
result = MenuModel(**CamelCaseUtil.transform_result(menu)) if menu else MenuModel()
return result
@classmethod
def list_to_tree(cls, permission_list: Sequence[SysMenu]) -> list[MenuTreeModel]:
"""
工具方法:根据菜单列表信息生成树形嵌套数据
:param permission_list: 菜单列表信息
:return: 菜单树形嵌套数据
"""
_permission_list = [
MenuTreeModel(id=item.menu_id, label=item.menu_name, parentId=item.parent_id) for item in permission_list
]
# 转成id为key的字典
mapping: dict[int, MenuTreeModel] = dict(zip([i.id for i in _permission_list], _permission_list, strict=False))
# 树容器
container: list[MenuTreeModel] = []
for d in _permission_list:
# 如果找不到父级项,则是根节点
parent = mapping.get(d.parent_id)
if parent is None:
container.append(d)
else:
children: list[MenuTreeModel] = parent.children
if not children:
children = []
children.append(d)
parent.children = children
return container
@@ -0,0 +1,126 @@
from typing import Any
from sqlalchemy.ext.asyncio import AsyncSession
from common.constant import CommonConstant
from common.vo import CrudResponseModel, PageModel
from exceptions.exception import ServiceException
from module_admin.dao.notice_dao import NoticeDao
from module_admin.entity.vo.notice_vo import DeleteNoticeModel, NoticeModel, NoticePageQueryModel
from utils.common_util import CamelCaseUtil
class NoticeService:
"""
通知公告管理模块服务层
"""
@classmethod
async def get_notice_list_services(
cls, query_db: AsyncSession, query_object: NoticePageQueryModel, is_page: bool = True
) -> PageModel | list[dict[str, Any]]:
"""
获取通知公告列表信息service
:param query_db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 通知公告列表信息对象
"""
notice_list_result = await NoticeDao.get_notice_list(query_db, query_object, is_page)
return notice_list_result
@classmethod
async def check_notice_unique_services(cls, query_db: AsyncSession, page_object: NoticeModel) -> bool:
"""
校验通知公告是否存在service
:param query_db: orm对象
:param page_object: 通知公告对象
:return: 校验结果
"""
notice_id = -1 if page_object.notice_id is None else page_object.notice_id
notice = await NoticeDao.get_notice_detail_by_info(query_db, page_object)
if notice and notice.notice_id != notice_id:
return CommonConstant.NOT_UNIQUE
return CommonConstant.UNIQUE
@classmethod
async def add_notice_services(cls, query_db: AsyncSession, page_object: NoticeModel) -> CrudResponseModel:
"""
新增通知公告信息service
:param query_db: orm对象
:param page_object: 新增通知公告对象
:return: 新增通知公告校验结果
"""
if not await cls.check_notice_unique_services(query_db, page_object):
raise ServiceException(message=f'新增通知公告{page_object.notice_title}失败,通知公告已存在')
try:
await NoticeDao.add_notice_dao(query_db, page_object)
await query_db.commit()
return CrudResponseModel(is_success=True, message='新增成功')
except Exception as e:
await query_db.rollback()
raise e
@classmethod
async def edit_notice_services(cls, query_db: AsyncSession, page_object: NoticeModel) -> CrudResponseModel:
"""
编辑通知公告信息service
:param query_db: orm对象
:param page_object: 编辑通知公告对象
:return: 编辑通知公告校验结果
"""
edit_notice = page_object.model_dump(exclude_unset=True)
notice_info = await cls.notice_detail_services(query_db, page_object.notice_id)
if notice_info.notice_id:
if not await cls.check_notice_unique_services(query_db, page_object):
raise ServiceException(message=f'修改通知公告{page_object.notice_title}失败,通知公告已存在')
try:
await NoticeDao.edit_notice_dao(query_db, edit_notice)
await query_db.commit()
return CrudResponseModel(is_success=True, message='更新成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='通知公告不存在')
@classmethod
async def delete_notice_services(cls, query_db: AsyncSession, page_object: DeleteNoticeModel) -> CrudResponseModel:
"""
删除通知公告信息service
:param query_db: orm对象
:param page_object: 删除通知公告对象
:return: 删除通知公告校验结果
"""
if page_object.notice_ids:
notice_id_list = page_object.notice_ids.split(',')
try:
for notice_id in notice_id_list:
await NoticeDao.delete_notice_dao(query_db, NoticeModel(noticeId=notice_id))
await query_db.commit()
return CrudResponseModel(is_success=True, message='删除成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='传入通知公告id为空')
@classmethod
async def notice_detail_services(cls, query_db: AsyncSession, notice_id: int) -> NoticeModel:
"""
获取通知公告详细信息service
:param query_db: orm对象
:param notice_id: 通知公告id
:return: 通知公告id对应的信息
"""
notice = await NoticeDao.get_notice_detail_by_id(query_db, notice_id=notice_id)
result = NoticeModel(**CamelCaseUtil.transform_result(notice)) if notice else NoticeModel()
return result
@@ -0,0 +1,78 @@
from typing import Any
import jwt
from fastapi import Request
from common.enums import RedisInitKeyConfig
from common.vo import CrudResponseModel
from config.env import AppConfig, JwtConfig
from exceptions.exception import ServiceException
from module_admin.entity.vo.online_vo import DeleteOnlineModel, OnlineQueryModel
from utils.common_util import CamelCaseUtil
class OnlineService:
"""
在线用户管理模块服务层
"""
@classmethod
async def get_online_list_services(cls, request: Request, query_object: OnlineQueryModel) -> list[dict[str, Any]]:
"""
获取在线用户表信息service
:param request: Request对象
:param query_object: 查询参数对象
:return: 在线用户列表信息
"""
access_token_keys = await request.app.state.redis.keys(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}*')
if not access_token_keys:
access_token_keys = []
access_token_values_list = [await request.app.state.redis.get(key) for key in access_token_keys]
online_info_list = []
for item in access_token_values_list:
payload = jwt.decode(item, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm])
online_dict = {
'token_id': payload.get('session_id') if AppConfig.app_same_time_login else payload.get('user_id'),
'user_name': payload.get('user_name'),
'dept_name': payload.get('dept_name'),
'ipaddr': payload.get('login_info').get('ipaddr'),
'login_location': payload.get('login_info').get('loginLocation'),
'browser': payload.get('login_info').get('browser'),
'os': payload.get('login_info').get('os'),
'login_time': payload.get('login_info').get('loginTime'),
}
if query_object.user_name and not query_object.ipaddr:
if query_object.user_name == payload.get('user_name'):
online_info_list = [online_dict]
break
elif not query_object.user_name and query_object.ipaddr:
if query_object.ipaddr == payload.get('login_info').get('ipaddr'):
online_info_list = [online_dict]
break
elif query_object.user_name and query_object.ipaddr:
if query_object.user_name == payload.get('user_name') and query_object.ipaddr == payload.get(
'login_info'
).get('ipaddr'):
online_info_list = [online_dict]
break
else:
online_info_list.append(online_dict)
return CamelCaseUtil.transform_result(online_info_list)
@classmethod
async def delete_online_services(cls, request: Request, page_object: DeleteOnlineModel) -> CrudResponseModel:
"""
强退在线用户信息service
:param request: Request对象
:param page_object: 强退在线用户对象
:return: 强退在线用户校验结果
"""
if page_object.token_ids:
token_id_list = page_object.token_ids.split(',')
for token_id in token_id_list:
await request.app.state.redis.delete(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{token_id}')
return CrudResponseModel(is_success=True, message='强退成功')
raise ServiceException(message='传入session_id为空')
@@ -0,0 +1,180 @@
from typing import Any
from sqlalchemy.ext.asyncio import AsyncSession
from common.constant import CommonConstant
from common.vo import CrudResponseModel, PageModel
from exceptions.exception import ServiceException
from module_admin.dao.post_dao import PostDao
from module_admin.entity.vo.post_vo import DeletePostModel, PostModel, PostPageQueryModel
from utils.common_util import CamelCaseUtil
from utils.excel_util import ExcelUtil
class PostService:
"""
岗位管理模块服务层
"""
@classmethod
async def get_post_list_services(
cls, query_db: AsyncSession, query_object: PostPageQueryModel, is_page: bool = False
) -> PageModel | list[dict[str, Any]]:
"""
获取岗位列表信息service
:param query_db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 岗位列表信息对象
"""
post_list_result = await PostDao.get_post_list(query_db, query_object, is_page)
return post_list_result
@classmethod
async def check_post_name_unique_services(cls, query_db: AsyncSession, page_object: PostModel) -> bool:
"""
检查岗位名称是否唯一service
:param query_db: orm对象
:param page_object: 岗位对象
:return: 校验结果
"""
post_id = -1 if page_object.post_id is None else page_object.post_id
post = await PostDao.get_post_detail_by_info(query_db, PostModel(postName=page_object.post_name))
if post and post.post_id != post_id:
return CommonConstant.NOT_UNIQUE
return CommonConstant.UNIQUE
@classmethod
async def check_post_code_unique_services(cls, query_db: AsyncSession, page_object: PostModel) -> bool:
"""
检查岗位编码是否唯一service
:param query_db: orm对象
:param page_object: 岗位对象
:return: 校验结果
"""
post_id = -1 if page_object.post_id is None else page_object.post_id
post = await PostDao.get_post_detail_by_info(query_db, PostModel(postCode=page_object.post_code))
if post and post.post_id != post_id:
return CommonConstant.NOT_UNIQUE
return CommonConstant.UNIQUE
@classmethod
async def add_post_services(cls, query_db: AsyncSession, page_object: PostModel) -> CrudResponseModel:
"""
新增岗位信息service
:param query_db: orm对象
:param page_object: 新增岗位对象
:return: 新增岗位校验结果
"""
if not await cls.check_post_name_unique_services(query_db, page_object):
raise ServiceException(message=f'新增岗位{page_object.post_name}失败,岗位名称已存在')
if not await cls.check_post_code_unique_services(query_db, page_object):
raise ServiceException(message=f'新增岗位{page_object.post_name}失败,岗位编码已存在')
try:
await PostDao.add_post_dao(query_db, page_object)
await query_db.commit()
return CrudResponseModel(is_success=True, message='新增成功')
except Exception as e:
await query_db.rollback()
raise e
@classmethod
async def edit_post_services(cls, query_db: AsyncSession, page_object: PostModel) -> CrudResponseModel:
"""
编辑岗位信息service
:param query_db: orm对象
:param page_object: 编辑岗位对象
:return: 编辑岗位校验结果
"""
edit_post = page_object.model_dump(exclude_unset=True)
post_info = await cls.post_detail_services(query_db, page_object.post_id)
if post_info.post_id:
if not await cls.check_post_name_unique_services(query_db, page_object):
raise ServiceException(message=f'修改岗位{page_object.post_name}失败,岗位名称已存在')
if not await cls.check_post_code_unique_services(query_db, page_object):
raise ServiceException(message=f'修改岗位{page_object.post_name}失败,岗位编码已存在')
try:
await PostDao.edit_post_dao(query_db, edit_post)
await query_db.commit()
return CrudResponseModel(is_success=True, message='更新成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='岗位不存在')
@classmethod
async def delete_post_services(cls, query_db: AsyncSession, page_object: DeletePostModel) -> CrudResponseModel:
"""
删除岗位信息service
:param query_db: orm对象
:param page_object: 删除岗位对象
:return: 删除岗位校验结果
"""
if page_object.post_ids:
post_id_list = page_object.post_ids.split(',')
try:
for post_id in post_id_list:
post = await cls.post_detail_services(query_db, int(post_id))
if (await PostDao.count_user_post_dao(query_db, int(post_id))) > 0:
raise ServiceException(message=f'{post.post_name}已分配,不能删除')
await PostDao.delete_post_dao(query_db, PostModel(postId=post_id))
await query_db.commit()
return CrudResponseModel(is_success=True, message='删除成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='传入岗位id为空')
@classmethod
async def post_detail_services(cls, query_db: AsyncSession, post_id: int) -> PostModel:
"""
获取岗位详细信息service
:param query_db: orm对象
:param post_id: 岗位id
:return: 岗位id对应的信息
"""
post = await PostDao.get_post_detail_by_id(query_db, post_id=post_id)
result = PostModel(**CamelCaseUtil.transform_result(post)) if post else PostModel()
return result
@staticmethod
async def export_post_list_services(post_list: list) -> bytes:
"""
导出岗位信息service
:param post_list: 岗位信息列表
:return: 岗位信息对应excel的二进制数据
"""
# 创建一个映射字典,将英文键映射到中文键
mapping_dict = {
'postId': '岗位编号',
'postCode': '岗位编码',
'postName': '岗位名称',
'postSort': '显示顺序',
'status': '状态',
'createBy': '创建者',
'createTime': '创建时间',
'updateBy': '更新者',
'updateTime': '更新时间',
'remark': '备注',
}
for item in post_list:
if item.get('status') == '0':
item['status'] = '正常'
else:
item['status'] = '停用'
binary_data = ExcelUtil.export_list2excel(post_list, mapping_dict)
return binary_data
@@ -0,0 +1,368 @@
from typing import Any
from sqlalchemy import ColumnElement
from sqlalchemy.ext.asyncio import AsyncSession
from common.constant import CommonConstant
from common.vo import CrudResponseModel, PageModel
from exceptions.exception import ServiceException
from module_admin.dao.role_dao import RoleDao
from module_admin.dao.user_dao import UserDao
from module_admin.entity.vo.role_vo import (
AddRoleModel,
DeleteRoleModel,
RoleDeptModel,
RoleDeptQueryModel,
RoleMenuModel,
RoleModel,
RolePageQueryModel,
)
from module_admin.entity.vo.user_vo import UserInfoModel, UserRolePageQueryModel
from utils.common_util import CamelCaseUtil
from utils.excel_util import ExcelUtil
class RoleService:
"""
角色管理模块服务层
"""
@classmethod
async def get_role_select_option_services(cls, query_db: AsyncSession) -> list[dict[str, Any]]:
"""
获取角色列表不分页信息service
:param query_db: orm对象
:return: 角色列表不分页信息对象
"""
role_list_result = await RoleDao.get_role_select_option_dao(query_db)
return CamelCaseUtil.transform_result(role_list_result)
@classmethod
async def get_role_dept_tree_services(cls, query_db: AsyncSession, role_id: int) -> RoleDeptQueryModel:
"""
根据角色id获取部门树信息service
:param query_db: orm对象
:param role_id: 角色id
:return: 当前角色id的部门树信息对象
"""
role = await cls.role_detail_services(query_db, role_id)
role_dept_list = await RoleDao.get_role_dept_dao(query_db, role)
checked_keys = [row.dept_id for row in role_dept_list]
result = RoleDeptQueryModel(checkedKeys=checked_keys)
return result
@classmethod
async def get_role_list_services(
cls,
query_db: AsyncSession,
query_object: RolePageQueryModel,
data_scope_sql: ColumnElement,
is_page: bool = False,
) -> PageModel | list[dict[str, Any]]:
"""
获取角色列表信息service
:param query_db: orm对象
:param query_object: 查询参数对象
:param data_scope_sql: 数据权限对应的查询sql语句
:param is_page: 是否开启分页
:return: 角色列表信息对象
"""
role_list_result = await RoleDao.get_role_list(query_db, query_object, data_scope_sql, is_page)
return role_list_result
@classmethod
async def check_role_allowed_services(cls, check_role: RoleModel) -> CrudResponseModel:
"""
校验角色是否允许操作service
:param check_role: 角色信息
:return: 校验结果
"""
if check_role.admin:
raise ServiceException(message='不允许操作超级管理员角色')
return CrudResponseModel(is_success=True, message='校验通过')
@classmethod
async def check_role_data_scope_services(
cls, query_db: AsyncSession, role_ids: str, data_scope_sql: ColumnElement
) -> None:
"""
校验角色是否有数据权限service
:param query_db: orm对象
:param role_ids: 角色id
:param data_scope_sql: 数据权限对应的查询sql语句
:return: 校验结果
"""
role_id_list = role_ids.split(',') if role_ids else []
if role_id_list:
for role_id in role_id_list:
roles = await RoleDao.get_role_list(
query_db, RolePageQueryModel(roleId=int(role_id)), data_scope_sql, is_page=False
)
if roles:
continue
raise ServiceException(message='没有权限访问角色数据')
@classmethod
async def check_role_name_unique_services(cls, query_db: AsyncSession, page_object: RoleModel) -> bool:
"""
校验角色名称是否唯一service
:param query_db: orm对象
:param page_object: 角色对象
:return: 校验结果
"""
role_id = -1 if page_object.role_id is None else page_object.role_id
role = await RoleDao.get_role_by_info(query_db, RoleModel(roleName=page_object.role_name))
if role and role.role_id != role_id:
return CommonConstant.NOT_UNIQUE
return CommonConstant.UNIQUE
@classmethod
async def check_role_key_unique_services(cls, query_db: AsyncSession, page_object: RoleModel) -> bool:
"""
校验角色权限字符是否唯一service
:param query_db: orm对象
:param page_object: 角色对象
:return: 校验结果
"""
role_id = -1 if page_object.role_id is None else page_object.role_id
role = await RoleDao.get_role_by_info(query_db, RoleModel(roleKey=page_object.role_key))
if role and role.role_id != role_id:
return CommonConstant.NOT_UNIQUE
return CommonConstant.UNIQUE
@classmethod
async def add_role_services(cls, query_db: AsyncSession, page_object: AddRoleModel) -> CrudResponseModel:
"""
新增角色信息service
:param query_db: orm对象
:param page_object: 新增角色对象
:return: 新增角色校验结果
"""
add_role = RoleModel(**page_object.model_dump(by_alias=True))
if not await cls.check_role_name_unique_services(query_db, page_object):
raise ServiceException(message=f'新增角色{page_object.role_name}失败,角色名称已存在')
if not await cls.check_role_key_unique_services(query_db, page_object):
raise ServiceException(message=f'新增角色{page_object.role_name}失败,角色权限已存在')
try:
add_result = await RoleDao.add_role_dao(query_db, add_role)
role_id = add_result.role_id
if page_object.menu_ids:
for menu in page_object.menu_ids:
await RoleDao.add_role_menu_dao(query_db, RoleMenuModel(roleId=role_id, menuId=menu))
await query_db.commit()
return CrudResponseModel(is_success=True, message='新增成功')
except Exception as e:
await query_db.rollback()
raise e
@classmethod
async def edit_role_services(cls, query_db: AsyncSession, page_object: AddRoleModel) -> CrudResponseModel:
"""
编辑角色信息service
:param query_db: orm对象
:param page_object: 编辑角色对象
:return: 编辑角色校验结果
"""
edit_role = page_object.model_dump(exclude_unset=True, exclude={'admin'})
if page_object.type != 'status':
del edit_role['menu_ids']
if page_object.type == 'status':
del edit_role['type']
role_info = await cls.role_detail_services(query_db, edit_role.get('role_id'))
if role_info:
if page_object.type != 'status':
if not await cls.check_role_name_unique_services(query_db, page_object):
raise ServiceException(message=f'修改角色{page_object.role_name}失败,角色名称已存在')
if not await cls.check_role_key_unique_services(query_db, page_object):
raise ServiceException(message=f'修改角色{page_object.role_name}失败,角色权限已存在')
try:
await RoleDao.edit_role_dao(query_db, edit_role)
if page_object.type != 'status':
await RoleDao.delete_role_menu_dao(query_db, RoleMenuModel(roleId=page_object.role_id))
if page_object.menu_ids:
for menu in page_object.menu_ids:
await RoleDao.add_role_menu_dao(
query_db, RoleMenuModel(roleId=page_object.role_id, menuId=menu)
)
await query_db.commit()
return CrudResponseModel(is_success=True, message='更新成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='角色不存在')
@classmethod
async def role_datascope_services(cls, query_db: AsyncSession, page_object: AddRoleModel) -> CrudResponseModel:
"""
分配角色数据权限service
:param query_db: orm对象
:param page_object: 角色数据权限对象
:return: 分配角色数据权限结果
"""
edit_role = page_object.model_dump(exclude_unset=True, exclude={'admin', 'dept_ids'})
role_info = await cls.role_detail_services(query_db, page_object.role_id)
if role_info.role_id:
try:
await RoleDao.edit_role_dao(query_db, edit_role)
await RoleDao.delete_role_dept_dao(query_db, RoleDeptModel(roleId=page_object.role_id))
if page_object.dept_ids and page_object.data_scope == '2':
for dept in page_object.dept_ids:
await RoleDao.add_role_dept_dao(
query_db, RoleDeptModel(roleId=page_object.role_id, deptId=dept)
)
await query_db.commit()
return CrudResponseModel(is_success=True, message='分配成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='角色不存在')
@classmethod
async def delete_role_services(cls, query_db: AsyncSession, page_object: DeleteRoleModel) -> CrudResponseModel:
"""
删除角色信息service
:param query_db: orm对象
:param page_object: 删除角色对象
:return: 删除角色校验结果
"""
if page_object.role_ids:
role_id_list = page_object.role_ids.split(',')
try:
for role_id in role_id_list:
role = await cls.role_detail_services(query_db, int(role_id))
if (await RoleDao.count_user_role_dao(query_db, int(role_id))) > 0:
raise ServiceException(message=f'角色{role.role_name}已分配,不能删除')
role_id_dict = {
'roleId': role_id,
'updateBy': page_object.update_by,
'updateTime': page_object.update_time,
}
await RoleDao.delete_role_menu_dao(query_db, RoleMenuModel(**role_id_dict))
await RoleDao.delete_role_dept_dao(query_db, RoleDeptModel(**role_id_dict))
await RoleDao.delete_role_dao(query_db, RoleModel(**role_id_dict))
await query_db.commit()
return CrudResponseModel(is_success=True, message='删除成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='传入角色id为空')
@classmethod
async def role_detail_services(cls, query_db: AsyncSession, role_id: int) -> RoleModel:
"""
获取角色详细信息service
:param query_db: orm对象
:param role_id: 角色id
:return: 角色id对应的信息
"""
role = await RoleDao.get_role_detail_by_id(query_db, role_id=role_id)
result = RoleModel(**CamelCaseUtil.transform_result(role)) if role else RoleModel()
return result
@staticmethod
async def export_role_list_services(role_list: list) -> bytes:
"""
导出角色列表信息service
:param role_list: 角色信息列表
:return: 角色列表信息对象
"""
# 创建一个映射字典,将英文键映射到中文键
mapping_dict = {
'roleId': '角色编号',
'roleName': '角色名称',
'roleKey': '权限字符',
'roleSort': '显示顺序',
'status': '状态',
'createBy': '创建者',
'createTime': '创建时间',
'updateBy': '更新者',
'updateTime': '更新时间',
'remark': '备注',
}
for item in role_list:
if item.get('status') == '0':
item['status'] = '正常'
else:
item['status'] = '停用'
binary_data = ExcelUtil.export_list2excel(role_list, mapping_dict)
return binary_data
@classmethod
async def get_role_user_allocated_list_services(
cls,
query_db: AsyncSession,
page_object: UserRolePageQueryModel,
data_scope_sql: ColumnElement,
is_page: bool = False,
) -> PageModel:
"""
根据角色id获取已分配用户列表
:param query_db: orm对象
:param page_object: 用户关联角色对象
:param data_scope_sql: 数据权限对应的查询sql语句
:param is_page: 是否开启分页
:return: 已分配用户列表
"""
query_user_list = await UserDao.get_user_role_allocated_list_by_role_id(
query_db, page_object, data_scope_sql, is_page
)
allocated_list = PageModel[UserInfoModel](
**{
**query_user_list.model_dump(by_alias=True),
'rows': [UserInfoModel(**row) for row in query_user_list.rows],
}
)
return allocated_list
@classmethod
async def get_role_user_unallocated_list_services(
cls,
query_db: AsyncSession,
page_object: UserRolePageQueryModel,
data_scope_sql: ColumnElement,
is_page: bool = False,
) -> PageModel:
"""
根据角色id获取未分配用户列表
:param query_db: orm对象
:param page_object: 用户关联角色对象
:param data_scope_sql: 数据权限对应的查询sql语句
:param is_page: 是否开启分页
:return: 未分配用户列表
"""
query_user_list = await UserDao.get_user_role_unallocated_list_by_role_id(
query_db, page_object, data_scope_sql, is_page
)
unallocated_list = PageModel[UserInfoModel](
**{
**query_user_list.model_dump(by_alias=True),
'rows': [UserInfoModel(**row) for row in query_user_list.rows],
}
)
return unallocated_list
@@ -0,0 +1,103 @@
import os
import platform
import socket
import time
import anyio
import psutil
from module_admin.entity.vo.server_vo import CpuInfo, MemoryInfo, PyInfo, ServerMonitorModel, SysFiles, SysInfo
from utils.common_util import bytes2human
class ServerService:
"""
服务监控模块服务层
"""
@staticmethod
async def get_server_monitor_info() -> ServerMonitorModel:
# CPU信息
# 获取CPU总核心数
cpu_num = psutil.cpu_count(logical=True)
cpu_usage_percent = psutil.cpu_times_percent()
cpu_used = cpu_usage_percent.user
cpu_sys = cpu_usage_percent.system
cpu_free = cpu_usage_percent.idle
cpu = CpuInfo(cpuNum=cpu_num, used=cpu_used, sys=cpu_sys, free=cpu_free)
# 内存信息
memory_info = psutil.virtual_memory()
memory_total = bytes2human(memory_info.total)
memory_used = bytes2human(memory_info.used)
memory_free = bytes2human(memory_info.free)
memory_usage = memory_info.percent
mem = MemoryInfo(total=memory_total, used=memory_used, free=memory_free, usage=memory_usage)
# 主机信息
# 获取主机名
hostname = socket.gethostname()
# 获取IP
computer_ip = socket.gethostbyname(hostname)
os_name = platform.platform()
computer_name = platform.node()
os_arch = platform.machine()
user_dir = str(await anyio.Path.cwd())
sys = SysInfo(
computerIp=computer_ip, computerName=computer_name, osArch=os_arch, osName=os_name, userDir=user_dir
)
# python解释器信息
current_pid = os.getpid()
current_process = psutil.Process(current_pid)
python_name = current_process.name()
python_version = platform.python_version()
python_home = current_process.exe()
start_time_stamp = current_process.create_time()
start_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(start_time_stamp))
current_time_stamp = time.time()
difference = current_time_stamp - start_time_stamp
# 将时间差转换为天、小时和分钟数
days = int(difference // (24 * 60 * 60)) # 每天的秒数
hours = int((difference % (24 * 60 * 60)) // (60 * 60)) # 每小时的秒数
minutes = int((difference % (60 * 60)) // 60) # 每分钟的秒数
run_time = f'{days}天{hours}小时{minutes}分钟'
# 获取当前Python程序的pid
pid = os.getpid()
# 获取该进程的内存信息
current_process_memory_info = psutil.Process(pid).memory_info()
py = PyInfo(
name=python_name,
version=python_version,
startTime=start_time,
runTime=run_time,
home=python_home,
total=bytes2human(memory_info.available),
used=bytes2human(current_process_memory_info.rss),
free=bytes2human(memory_info.available - current_process_memory_info.rss),
usage=round((current_process_memory_info.rss / memory_info.available) * 100, 2),
)
# 磁盘信息
io = psutil.disk_partitions()
sys_files = []
for i in io:
try:
o = psutil.disk_usage(i.device)
disk_data = SysFiles(
dirName=i.device,
sysTypeName=i.fstype,
typeName='本地固定磁盘(' + i.mountpoint.replace('\\', '') + ')',
total=bytes2human(o.total),
used=bytes2human(o.used),
free=bytes2human(o.free),
usage=f'{psutil.disk_usage(i.device).percent}%',
)
sys_files.append(disk_data)
except Exception: # noqa: PERF203
# 忽略所有异常,跳过有问题的磁盘
continue
result = ServerMonitorModel(cpu=cpu, mem=mem, sys=sys, py=py, sysFiles=sys_files)
return result
@@ -0,0 +1,44 @@
from fastapi import Request
from module_admin.entity.vo.transport_crypto_vo import (
TransportCryptoFrontendConfigModel,
TransportCryptoMonitorModel,
TransportCryptoPublicKeyModel,
)
from utils.transport_crypto_util import TransportCryptoMonitorUtil, TransportCryptoUtil
class TransportCryptoService:
"""
传输加密模块服务层
"""
@classmethod
async def get_transport_frontend_config_services(cls) -> TransportCryptoFrontendConfigModel:
"""
获取前端传输加密运行配置service
:return: 前端传输加密运行配置
"""
return TransportCryptoFrontendConfigModel.model_validate(TransportCryptoUtil.build_frontend_config_payload())
@classmethod
async def get_transport_public_key_services(cls) -> TransportCryptoPublicKeyModel:
"""
获取传输加密公钥service
:return: 传输加密公钥信息
"""
return TransportCryptoPublicKeyModel.model_validate(TransportCryptoUtil.build_public_key_payload())
@classmethod
async def get_transport_crypto_monitor_info_services(cls, request: Request) -> TransportCryptoMonitorModel:
"""
获取传输加密监控信息service
:param request: Request对象
:return: 传输加密监控信息
"""
transport_crypto_monitor_info = await TransportCryptoMonitorUtil.get_snapshot(request.app)
return TransportCryptoMonitorModel.model_validate(transport_crypto_monitor_info)
@@ -0,0 +1,679 @@
import io
from datetime import datetime
from typing import Any
import pandas as pd
from fastapi import Request, UploadFile
from sqlalchemy import ColumnElement
from sqlalchemy.ext.asyncio import AsyncSession
from common.constant import CommonConstant
from common.vo import CrudResponseModel, PageModel
from exceptions.exception import ServiceException
from module_admin.dao.user_dao import UserDao
from module_admin.entity.do.user_do import SysUserRole
from module_admin.entity.vo.post_vo import PostPageQueryModel
from module_admin.entity.vo.user_vo import (
AddUserModel,
CrudUserRoleModel,
CurrentUserModel,
DeleteUserModel,
EditUserModel,
ResetUserModel,
SelectedRoleModel,
UserDetailModel,
UserInfoModel,
UserModel,
UserPageQueryModel,
UserPostModel,
UserProfileModel,
UserRoleModel,
UserRoleQueryModel,
UserRoleResponseModel,
UserRowModel,
)
from module_admin.service.config_service import ConfigService
from module_admin.service.dept_service import DeptService
from module_admin.service.post_service import PostService
from module_admin.service.role_service import RoleService
from utils.common_util import CamelCaseUtil
from utils.excel_util import ExcelUtil
from utils.pwd_util import PwdUtil
class UserService:
"""
用户管理模块服务层
"""
@classmethod
async def get_user_list_services(
cls,
query_db: AsyncSession,
query_object: UserPageQueryModel,
data_scope_sql: ColumnElement,
is_page: bool = False,
) -> PageModel[UserRowModel] | list[dict[str, Any]]:
"""
获取用户列表信息service
:param query_db: orm对象
:param query_object: 查询参数对象
:param data_scope_sql: 数据权限对应的查询sql语句
:param is_page: 是否开启分页
:return: 用户列表信息对象
"""
query_result = await UserDao.get_user_list(query_db, query_object, data_scope_sql, is_page)
if is_page:
user_list_result = PageModel[UserRowModel](
**{
**query_result.model_dump(by_alias=True),
'rows': [{**row[0], 'dept': row[1]} for row in query_result.rows],
}
)
else:
user_list_result = []
if query_result:
user_list_result = [{**row[0], 'dept': row[1]} for row in query_result]
return user_list_result
@classmethod
async def check_user_allowed_services(cls, check_user: UserModel) -> CrudResponseModel:
"""
校验用户是否允许操作service
:param check_user: 用户信息
:return: 校验结果
"""
if check_user.admin:
raise ServiceException(message='不允许操作超级管理员用户')
return CrudResponseModel(is_success=True, message='校验通过')
@classmethod
async def check_user_data_scope_services(
cls, query_db: AsyncSession, user_id: int, data_scope_sql: ColumnElement
) -> CrudResponseModel:
"""
校验用户数据权限service
:param query_db: orm对象
:param user_id: 用户id
:param data_scope_sql: 数据权限对应的查询sql语句
:return: 校验结果
"""
users = await UserDao.get_user_list(query_db, UserPageQueryModel(userId=user_id), data_scope_sql, is_page=False)
if users:
return CrudResponseModel(is_success=True, message='校验通过')
raise ServiceException(message='没有权限访问用户数据')
@classmethod
async def check_user_name_unique_services(cls, query_db: AsyncSession, page_object: UserModel) -> bool:
"""
校验用户名是否唯一service
:param query_db: orm对象
:param page_object: 用户对象
:return: 校验结果
"""
user_id = -1 if page_object.user_id is None else page_object.user_id
user = await UserDao.get_user_by_info(query_db, UserModel(userName=page_object.user_name))
if user and user.user_id != user_id:
return CommonConstant.NOT_UNIQUE
return CommonConstant.UNIQUE
@classmethod
async def check_phonenumber_unique_services(cls, query_db: AsyncSession, page_object: UserModel) -> bool:
"""
校验用户手机号是否唯一service
:param query_db: orm对象
:param page_object: 用户对象
:return: 校验结果
"""
user_id = -1 if page_object.user_id is None else page_object.user_id
user = await UserDao.get_user_by_info(query_db, UserModel(phonenumber=page_object.phonenumber))
if user and user.user_id != user_id:
return CommonConstant.NOT_UNIQUE
return CommonConstant.UNIQUE
@classmethod
async def check_email_unique_services(cls, query_db: AsyncSession, page_object: UserModel) -> bool:
"""
校验用户邮箱是否唯一service
:param query_db: orm对象
:param page_object: 用户对象
:return: 校验结果
"""
user_id = -1 if page_object.user_id is None else page_object.user_id
user = await UserDao.get_user_by_info(query_db, UserModel(email=page_object.email))
if user and user.user_id != user_id:
return CommonConstant.NOT_UNIQUE
return CommonConstant.UNIQUE
@classmethod
async def add_user_services(cls, query_db: AsyncSession, page_object: AddUserModel) -> CrudResponseModel:
"""
新增用户信息service
:param query_db: orm对象
:param page_object: 新增用户对象
:return: 新增用户校验结果
"""
add_user = UserModel(**page_object.model_dump(by_alias=True))
if not await cls.check_user_name_unique_services(query_db, page_object):
raise ServiceException(message=f'新增用户{page_object.user_name}失败,登录账号已存在')
if page_object.phonenumber and not await cls.check_phonenumber_unique_services(query_db, page_object):
raise ServiceException(message=f'新增用户{page_object.user_name}失败,手机号码已存在')
if page_object.email and not await cls.check_email_unique_services(query_db, page_object):
raise ServiceException(message=f'新增用户{page_object.user_name}失败,邮箱账号已存在')
try:
add_result = await UserDao.add_user_dao(query_db, add_user)
user_id = add_result.user_id
if page_object.role_ids:
for role in page_object.role_ids:
await UserDao.add_user_role_dao(query_db, UserRoleModel(userId=user_id, roleId=role))
if page_object.post_ids:
for post in page_object.post_ids:
await UserDao.add_user_post_dao(query_db, UserPostModel(userId=user_id, postId=post))
await query_db.commit()
return CrudResponseModel(is_success=True, message='新增成功')
except Exception as e:
await query_db.rollback()
raise e
@classmethod
def _deal_edit_user(cls, page_object: EditUserModel, edit_user: dict[str, Any]) -> None:
"""
处理编辑用户字典
:param page_object: 编辑用户对象
:param edit_user: 编辑用户字典
:return: None
"""
if page_object.type not in ['status', 'avatar', 'pwd']:
del edit_user['role_ids']
del edit_user['post_ids']
del edit_user['role']
else:
del edit_user['type']
@classmethod
async def edit_user_services(cls, query_db: AsyncSession, page_object: EditUserModel) -> CrudResponseModel:
"""
编辑用户信息service
:param query_db: orm对象
:param page_object: 编辑用户对象
:return: 编辑用户校验结果
"""
edit_user = page_object.model_dump(exclude_unset=True, exclude={'admin'})
cls._deal_edit_user(page_object, edit_user)
user_info = await cls.user_detail_services(query_db, edit_user.get('user_id'))
if user_info.data and user_info.data.user_id:
if page_object.type not in ['status', 'avatar', 'pwd']:
if not await cls.check_user_name_unique_services(query_db, page_object):
raise ServiceException(message=f'修改用户{page_object.user_name}失败,登录账号已存在')
if page_object.phonenumber and not await cls.check_phonenumber_unique_services(query_db, page_object):
raise ServiceException(message=f'修改用户{page_object.user_name}失败,手机号码已存在')
if page_object.email and not await cls.check_email_unique_services(query_db, page_object):
raise ServiceException(message=f'修改用户{page_object.user_name}失败,邮箱账号已存在')
try:
await UserDao.edit_user_dao(query_db, edit_user)
if page_object.type not in {'status', 'avatar', 'pwd'}:
await UserDao.delete_user_role_dao(query_db, UserRoleModel(userId=page_object.user_id))
await UserDao.delete_user_post_dao(query_db, UserPostModel(userId=page_object.user_id))
if page_object.role_ids:
for role in page_object.role_ids:
await UserDao.add_user_role_dao(
query_db, UserRoleModel(userId=page_object.user_id, roleId=role)
)
if page_object.post_ids:
for post in page_object.post_ids:
await UserDao.add_user_post_dao(
query_db, UserPostModel(userId=page_object.user_id, postId=post)
)
await query_db.commit()
return CrudResponseModel(is_success=True, message='更新成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='用户不存在')
@classmethod
async def delete_user_services(cls, query_db: AsyncSession, page_object: DeleteUserModel) -> CrudResponseModel:
"""
删除用户信息service
:param query_db: orm对象
:param page_object: 删除用户对象
:return: 删除用户校验结果
"""
if page_object.user_ids:
user_id_list = page_object.user_ids.split(',')
try:
for user_id in user_id_list:
user_id_dict = {
'userId': user_id,
'updateBy': page_object.update_by,
'updateTime': page_object.update_time,
}
await UserDao.delete_user_role_dao(query_db, UserRoleModel(**user_id_dict))
await UserDao.delete_user_post_dao(query_db, UserPostModel(**user_id_dict))
await UserDao.delete_user_dao(query_db, UserModel(**user_id_dict))
await query_db.commit()
return CrudResponseModel(is_success=True, message='删除成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='传入用户id为空')
@classmethod
async def user_detail_services(cls, query_db: AsyncSession, user_id: int | str) -> UserDetailModel:
"""
获取用户详细信息service
:param query_db: orm对象
:param user_id: 用户id
:return: 用户id对应的信息
"""
posts = await PostService.get_post_list_services(query_db, PostPageQueryModel(), is_page=False)
roles = await RoleService.get_role_select_option_services(query_db)
if user_id != '':
query_user = await UserDao.get_user_detail_by_id(query_db, user_id=user_id)
post_ids = ','.join([str(row.post_id) for row in query_user.get('user_post_info')])
post_ids_list = [row.post_id for row in query_user.get('user_post_info')]
role_ids = ','.join([str(row.role_id) for row in query_user.get('user_role_info')])
role_ids_list = [row.role_id for row in query_user.get('user_role_info')]
return UserDetailModel(
data=UserInfoModel(
**CamelCaseUtil.transform_result(query_user.get('user_basic_info')),
postIds=post_ids,
roleIds=role_ids,
dept=CamelCaseUtil.transform_result(query_user.get('user_dept_info')),
role=CamelCaseUtil.transform_result(query_user.get('user_role_info')),
),
postIds=post_ids_list,
posts=posts,
roleIds=role_ids_list,
roles=roles,
)
return UserDetailModel(posts=posts, roles=roles)
@classmethod
async def user_profile_services(cls, query_db: AsyncSession, user_id: int) -> UserProfileModel:
"""
获取用户个人详细信息service
:param query_db: orm对象
:param user_id: 用户id
:return: 用户id对应的信息
"""
query_user = await UserDao.get_user_detail_by_id(query_db, user_id=user_id)
post_ids = ','.join([str(row.post_id) for row in query_user.get('user_post_info')])
post_group = ','.join([row.post_name for row in query_user.get('user_post_info')])
role_ids = ','.join([str(row.role_id) for row in query_user.get('user_role_info')])
role_group = ','.join([row.role_name for row in query_user.get('user_role_info')])
return UserProfileModel(
data=UserInfoModel(
**CamelCaseUtil.transform_result(query_user.get('user_basic_info')),
postIds=post_ids,
roleIds=role_ids,
dept=CamelCaseUtil.transform_result(query_user.get('user_dept_info')),
role=CamelCaseUtil.transform_result(query_user.get('user_role_info')),
),
postGroup=post_group,
roleGroup=role_group,
)
@classmethod
async def reset_user_services(cls, query_db: AsyncSession, page_object: ResetUserModel) -> CrudResponseModel:
"""
重置用户密码service
:param query_db: orm对象
:param page_object: 重置用户对象
:return: 重置用户校验结果
"""
reset_user = page_object.model_dump(exclude_unset=True, exclude={'admin'})
if page_object.old_password:
user = (await UserDao.get_user_detail_by_id(query_db, user_id=page_object.user_id)).get('user_basic_info')
if not PwdUtil.verify_password(page_object.old_password, user.password):
raise ServiceException(message='修改密码失败,旧密码错误')
if PwdUtil.verify_password(page_object.password, user.password):
raise ServiceException(message='新密码不能与旧密码相同')
del reset_user['old_password']
if page_object.sms_code and page_object.session_id:
del reset_user['sms_code']
del reset_user['session_id']
try:
reset_user['password'] = PwdUtil.get_password_hash(page_object.password)
await UserDao.edit_user_dao(query_db, reset_user)
await query_db.commit()
return CrudResponseModel(is_success=True, message='重置成功')
except Exception as e:
await query_db.rollback()
raise e
@classmethod
def _set_row_sex_value(cls, row: pd.Series) -> None:
"""
设置行性别值
:param row: 行数据
:return: None
"""
if row['sex'] == '男':
row['sex'] = '0'
if row['sex'] == '女':
row['sex'] = '1'
if row['sex'] == '未知':
row['sex'] = '2'
@classmethod
def _set_row_status_value(cls, row: pd.Series) -> None:
"""
设置行状态值
:param row: 行数据
:return: None
"""
if row['status'] == '正常':
row['status'] = '0'
if row['status'] == '停用':
row['status'] = '1'
@classmethod
async def batch_import_user_services(
cls,
request: Request,
query_db: AsyncSession,
file: UploadFile,
update_support: bool,
current_user: CurrentUserModel,
user_data_scope_sql: ColumnElement,
dept_data_scope_sql: ColumnElement,
) -> CrudResponseModel:
"""
批量导入用户service
:param request: Request对象
:param query_db: orm对象
:param file: 用户导入文件对象
:param update_support: 用户存在时是否更新
:param current_user: 当前用户对象
:param user_data_scope_sql: 用户数据权限sql
:param dept_data_scope_sql: 部门数据权限sql
:return: 批量导入用户结果
"""
header_dict = {
'部门编号': 'dept_id',
'登录名称': 'user_name',
'用户名称': 'nick_name',
'用户邮箱': 'email',
'手机号码': 'phonenumber',
'用户性别': 'sex',
'帐号状态': 'status',
}
contents = await file.read()
df = pd.read_excel(io.BytesIO(contents))
await file.close()
df.rename(columns=header_dict, inplace=True)
add_error_result = []
count = 0
try:
for _index, row in df.iterrows():
count = count + 1
cls._set_row_sex_value(row)
cls._set_row_status_value(row)
add_user = UserModel(
deptId=row['dept_id'],
userName=row['user_name'],
password=PwdUtil.get_password_hash(
await ConfigService.query_config_list_from_cache_services(
request.app.state.redis, 'sys.user.initPassword'
)
),
nickName=row['nick_name'],
email=row['email'],
phonenumber=str(row['phonenumber']),
sex=row['sex'],
status=row['status'],
createBy=current_user.user.user_name,
createTime=datetime.now(),
updateBy=current_user.user.user_name,
updateTime=datetime.now(),
)
user_info = await UserDao.get_user_by_info(query_db, UserModel(userName=row['user_name']))
if user_info:
if update_support:
edit_user_model = UserModel(
userId=user_info.user_id,
deptId=row['dept_id'],
userName=row['user_name'],
nickName=row['nick_name'],
email=row['email'],
phonenumber=str(row['phonenumber']),
sex=row['sex'],
status=row['status'],
updateBy=current_user.user.user_name,
updateTime=datetime.now(),
)
edit_user_model.validate_fields()
await cls.check_user_allowed_services(edit_user_model)
if not current_user.user.admin:
await cls.check_user_data_scope_services(
query_db, edit_user_model.user_id, user_data_scope_sql
)
await DeptService.check_dept_data_scope_services(
query_db, edit_user_model.dept_id, dept_data_scope_sql
)
edit_user = edit_user_model.model_dump(exclude_unset=True)
await UserDao.edit_user_dao(query_db, edit_user)
else:
add_error_result.append(f'{count}.用户账号{row["user_name"]}已存在')
else:
add_user.validate_fields()
if not current_user.user.admin:
await DeptService.check_dept_data_scope_services(
query_db, add_user.dept_id, dept_data_scope_sql
)
await UserDao.add_user_dao(query_db, add_user)
await query_db.commit()
return CrudResponseModel(is_success=True, message='\n'.join(add_error_result))
except Exception as e:
await query_db.rollback()
raise e
@staticmethod
async def get_user_import_template_services() -> bytes:
"""
获取用户导入模板service
:return: 用户导入模板excel的二进制数据
"""
header_list = ['部门编号', '登录名称', '用户名称', '用户邮箱', '手机号码', '用户性别', '帐号状态']
selector_header_list = ['用户性别', '帐号状态']
option_list = [{'用户性别': ['男', '女', '未知']}, {'帐号状态': ['正常', '停用']}]
binary_data = ExcelUtil.get_excel_template(
header_list=header_list, selector_header_list=selector_header_list, option_list=option_list
)
return binary_data
@staticmethod
async def export_user_list_services(user_list: list) -> bytes:
"""
导出用户信息service
:param user_list: 用户信息列表
:return: 用户信息对应excel的二进制数据
"""
# 创建一个映射字典,将英文键映射到中文键
mapping_dict = {
'userId': '用户编号',
'userName': '用户名称',
'nickName': '用户昵称',
'deptName': '部门',
'email': '邮箱地址',
'phonenumber': '手机号码',
'sex': '性别',
'status': '状态',
'createBy': '创建者',
'createTime': '创建时间',
'updateBy': '更新者',
'updateTime': '更新时间',
'remark': '备注',
}
for item in user_list:
item['deptName'] = item.get('dept').get('deptName')
if item.get('status') == '0':
item['status'] = '正常'
else:
item['status'] = '停用'
if item.get('sex') == '0':
item['sex'] = '男'
elif item.get('sex') == '1':
item['sex'] = '女'
else:
item['sex'] = '未知'
binary_data = ExcelUtil.export_list2excel(user_list, mapping_dict)
return binary_data
@classmethod
async def get_user_role_allocated_list_services(
cls, query_db: AsyncSession, page_object: UserRoleQueryModel
) -> UserRoleResponseModel:
"""
根据用户id获取已分配角色列表
:param query_db: orm对象
:param page_object: 用户关联角色对象
:return: 已分配角色列表
"""
query_user = await UserDao.get_user_detail_by_id(query_db, page_object.user_id)
post_ids = ','.join([str(row.post_id) for row in query_user.get('user_post_info')])
role_ids = ','.join([str(row.role_id) for row in query_user.get('user_role_info')])
user = UserInfoModel(
**CamelCaseUtil.transform_result(query_user.get('user_basic_info')),
postIds=post_ids,
roleIds=role_ids,
dept=CamelCaseUtil.transform_result(query_user.get('user_dept_info')),
role=CamelCaseUtil.transform_result(query_user.get('user_role_info')),
)
query_role_list = [
SelectedRoleModel(**row) for row in await RoleService.get_role_select_option_services(query_db)
]
for model_a in query_role_list:
for model_b in user.role:
if model_a.role_id == model_b.role_id:
model_a.flag = True
result = UserRoleResponseModel(roles=query_role_list, user=user)
return result
@classmethod
async def add_user_role_services(cls, query_db: AsyncSession, page_object: CrudUserRoleModel) -> CrudResponseModel:
"""
新增用户关联角色信息service
:param query_db: orm对象
:param page_object: 新增用户关联角色对象
:return: 新增用户关联角色校验结果
"""
if page_object.user_id and page_object.role_ids:
role_id_list = page_object.role_ids.split(',')
try:
await UserDao.delete_user_role_by_user_and_role_dao(query_db, UserRoleModel(userId=page_object.user_id))
for role_id in role_id_list:
await UserDao.add_user_role_dao(query_db, UserRoleModel(userId=page_object.user_id, roleId=role_id))
await query_db.commit()
return CrudResponseModel(is_success=True, message='分配成功')
except Exception as e:
await query_db.rollback()
raise e
elif page_object.user_id and not page_object.role_ids:
try:
await UserDao.delete_user_role_by_user_and_role_dao(query_db, UserRoleModel(userId=page_object.user_id))
await query_db.commit()
return CrudResponseModel(is_success=True, message='分配成功')
except Exception as e:
await query_db.rollback()
raise e
elif page_object.user_ids and page_object.role_id:
user_id_list = page_object.user_ids.split(',')
try:
for user_id in user_id_list:
user_role = await cls.detail_user_role_services(
query_db, UserRoleModel(userId=user_id, roleId=page_object.role_id)
)
if user_role:
continue
await UserDao.add_user_role_dao(query_db, UserRoleModel(userId=user_id, roleId=page_object.role_id))
await query_db.commit()
return CrudResponseModel(is_success=True, message='新增成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='不满足新增条件')
@classmethod
async def delete_user_role_services(
cls, query_db: AsyncSession, page_object: CrudUserRoleModel
) -> CrudResponseModel:
"""
删除用户关联角色信息service
:param query_db: orm对象
:param page_object: 删除用户关联角色对象
:return: 删除用户关联角色校验结果
"""
if (page_object.user_id and page_object.role_id) or (page_object.user_ids and page_object.role_id):
if page_object.user_id and page_object.role_id:
try:
await UserDao.delete_user_role_by_user_and_role_dao(
query_db, UserRoleModel(userId=page_object.user_id, roleId=page_object.role_id)
)
await query_db.commit()
return CrudResponseModel(is_success=True, message='删除成功')
except Exception as e:
await query_db.rollback()
raise e
elif page_object.user_ids and page_object.role_id:
user_id_list = page_object.user_ids.split(',')
try:
for user_id in user_id_list:
await UserDao.delete_user_role_by_user_and_role_dao(
query_db, UserRoleModel(userId=user_id, roleId=page_object.role_id)
)
await query_db.commit()
return CrudResponseModel(is_success=True, message='删除成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='不满足删除条件')
else:
raise ServiceException(message='传入用户角色关联信息为空')
@classmethod
async def detail_user_role_services(cls, query_db: AsyncSession, page_object: UserRoleModel) -> SysUserRole | None:
"""
获取用户关联角色详细信息service
:param query_db: orm对象
:param page_object: 用户关联角色对象
:return: 用户关联角色详细信息
"""
user_role = await UserDao.get_user_role_detail(query_db, page_object)
return user_role