26 Commits

Author SHA1 Message Date
insistence
303612eed9 !6 RuoYi-Vue3-FastAPI v1.0.3
Merge pull request !6 from insistence/develop
2024-03-04 08:50:19 +00:00
insistence
dcb1f4d13c docs: 更新README文档 2024-03-04 15:50:48 +08:00
insistence
44ddc8c3a8 chore: 升级版本至1.0.3 2024-03-04 15:50:09 +08:00
insistence
70f6f8a471 fix: 修复添加和编辑菜单页面中是否缓存和是否外链字段回显异常的问题 #I95KBK 2024-03-04 15:49:45 +08:00
insistence
2a45df71cd fix: 修复外链菜单无法打开的问题 #I95KBK 2024-03-04 15:49:05 +08:00
insistence
eabeb705c4 feat: 账号密码登录新增IP黑名单校验 2024-03-04 15:47:13 +08:00
insistence
e9ad084dff !5 RuoYi-Vue3-FastAPI v1.0.2
Merge pull request !5 from insistence/develop
2024-02-18 07:12:52 +00:00
insistence
2e6c648126 docs: 更新README文档 2024-02-18 15:05:26 +08:00
insistence
153982436d chore: 升级版本号 2024-02-18 15:00:03 +08:00
insistence
45c3fa18e0 feat: 新增按角色校验接口权限依赖 2024-02-18 11:10:51 +08:00
insistence
3361fca5d2 feat: 用户接口权限校验增加列表接收参数,实现同一接口支持多个权限标识校验 2024-02-18 11:10:03 +08:00
insistence
b65aa4eea7 fix: 修复用户管理和部门管理模块数据权限异常的问题 2024-02-18 11:09:08 +08:00
insistence
7c4f0d1cb3 chore: 调整菜单管理模块部分接口权限标识 2024-02-18 11:08:53 +08:00
insistence
00520bc227 chore: 调整角色管理模块部分接口权限标识 2024-02-18 11:08:36 +08:00
insistence
bb94d38d53 chore: 调整日志管理模块部分接口权限标识 2024-02-18 11:08:21 +08:00
insistence
3ac12cab8c chore: 调整定时任务模块部分接口权限标识 2024-02-18 11:08:07 +08:00
insistence
4ea6fa7817 chore: 调整字典管理模块部分接口权限标识 2024-02-18 11:07:52 +08:00
insistence
fa065dfe45 chore: 调整部门管理模块部分接口权限标识 2024-02-18 11:07:36 +08:00
insistence
0a3102bdcf chore: 调整参数设置模块部分接口权限标识 2024-02-18 11:07:14 +08:00
insistence
b109d5abe3 !4 RuoYi-Vue3-FastAPI v1.0.1
Merge pull request !4 from insistence/develop
2024-02-04 09:10:12 +00:00
insistence
d828433a79 feat: 日志管理模块新增字段排序查询 2024-02-04 17:09:20 +08:00
insistence
d60d6ae8e5 !3 RuoYi-Vue3-FastAPI v1.0.1
Merge pull request !3 from insistence/develop
2024-02-04 05:38:29 +00:00
insistence
6a285068cf docs: 更新README文档 2024-02-04 13:32:27 +08:00
insistence
e70e9c2dcd chore: 删除多余文件 2024-02-04 13:32:12 +08:00
insistence
76ea949d32 feat: 日志管理模块新增字段排序查询 2024-02-04 11:35:44 +08:00
insistence
6e2d62a73d chore: 升级fastapi版本为0.109.1,修复一些安全性问题 2024-02-04 11:35:25 +08:00
21 changed files with 128 additions and 145 deletions

View File

@@ -1,17 +1,20 @@
<p align="center"> <p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png"> <img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
</p> </p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi-Vue3-FastAPI v1.0.0</h1> <h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi-Vue3-FastAPI v1.0.3</h1>
<h4 align="center">基于RuoYi-Vue3+FastAPI前后端分离的快速开发框架</h4> <h4 align="center">基于RuoYi-Vue3+FastAPI前后端分离的快速开发框架</h4>
<p align="center"> <p align="center">
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/stargazers"><img src="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/badge/star.svg?theme=dark"></a> <a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/stargazers"><img src="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/badge/star.svg?theme=dark"></a>
<a href="https://github.com/insistence/RuoYi-Vue3-FastAPI"><img src="https://img.shields.io/github/stars/insistence/RuoYi-Vue3-FastAPI?style=social"></a> <a href="https://github.com/insistence/RuoYi-Vue3-FastAPI"><img src="https://img.shields.io/github/stars/insistence/RuoYi-Vue3-FastAPI?style=social"></a>
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI"><img src="https://img.shields.io/badge/RuoYiVue3FastAPI-v1.0.0-brightgreen.svg"></a> <a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI"><img src="https://img.shields.io/badge/RuoYiVue3FastAPI-v1.0.3-brightgreen.svg"></a>
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a> <a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
<img src="https://img.shields.io/badge/python-≥3.8-blue"> <img src="https://img.shields.io/badge/python-≥3.8-blue">
<img src="https://img.shields.io/badge/MySQL-≥5.7-blue"> <img src="https://img.shields.io/badge/MySQL-≥5.7-blue">
</p> </p>
## 平台简介 ## 平台简介
RuoYi-Vue-FastAPI是一套全部开源的快速开发平台毫无保留给个人及企业免费使用。 RuoYi-Vue-FastAPI是一套全部开源的快速开发平台毫无保留给个人及企业免费使用。

View File

@@ -2,7 +2,7 @@
# 应用运行环境 # 应用运行环境
APP_ENV = 'dev' APP_ENV = 'dev'
# 应用名称 # 应用名称
APP_NAME = 'RuoYi-FasAPI' APP_NAME = 'RuoYi-FastAPI'
# 应用代理路径 # 应用代理路径
APP_ROOT_PATH = '/dev-api' APP_ROOT_PATH = '/dev-api'
# 应用主机 # 应用主机
@@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0'
# 应用端口 # 应用端口
APP_PORT = 9099 APP_PORT = 9099
# 应用版本 # 应用版本
APP_VERSION= '1.0.0' APP_VERSION= '1.0.3'
# 应用是否开启热重载 # 应用是否开启热重载
APP_RELOAD = true APP_RELOAD = true

View File

@@ -2,7 +2,7 @@
# 应用运行环境 # 应用运行环境
APP_ENV = 'prod' APP_ENV = 'prod'
# 应用名称 # 应用名称
APP_NAME = 'RuoYi-FasAPI' APP_NAME = 'RuoYi-FastAPI'
# 应用代理路径 # 应用代理路径
APP_ROOT_PATH = '/prod-api' APP_ROOT_PATH = '/prod-api'
# 应用主机 # 应用主机
@@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0'
# 应用端口 # 应用端口
APP_PORT = 9099 APP_PORT = 9099
# 应用版本 # 应用版本
APP_VERSION= '1.0.0' APP_VERSION= '1.0.3'
# 应用是否开启热重载 # 应用是否开启热重载
APP_RELOAD = false APP_RELOAD = false

View File

@@ -1,4 +1,5 @@
from fastapi import Depends from fastapi import Depends
from typing import Union, List
from module_admin.entity.vo.user_vo import CurrentUserModel from module_admin.entity.vo.user_vo import CurrentUserModel
from module_admin.service.login_service import LoginService from module_admin.service.login_service import LoginService
from exceptions.exception import PermissionException from exceptions.exception import PermissionException
@@ -7,13 +8,52 @@ from exceptions.exception import PermissionException
class CheckUserInterfaceAuth: class CheckUserInterfaceAuth:
""" """
校验当前用户是否具有相应的接口权限 校验当前用户是否具有相应的接口权限
:param perm: 权限标识
:param is_strict: 当传入的权限标识是list类型时是否开启严格模式开启表示会校验列表中的每一个权限标识所有的校验结果都需要为True才会通过
""" """
def __init__(self, perm_str: str = 'common'): def __init__(self, perm: Union[str, List], is_strict: bool = False):
self.perm_str = perm_str self.perm = perm
self.is_strict = is_strict
def __call__(self, current_user: CurrentUserModel = Depends(LoginService.get_current_user)): def __call__(self, current_user: CurrentUserModel = Depends(LoginService.get_current_user)):
user_auth_list = current_user.permissions user_auth_list = current_user.permissions
user_auth_list.append('common') if '*:*:*' in user_auth_list:
if '*:*:*' in user_auth_list or self.perm_str in user_auth_list: return True
if isinstance(self.perm, str):
if self.perm in user_auth_list:
return True
if isinstance(self.perm, list):
if self.is_strict:
if all([perm_str in user_auth_list for perm_str in self.perm]):
return True
else:
if any([perm_str in user_auth_list for perm_str in self.perm]):
return True return True
raise PermissionException(data="", message="该用户无此接口权限") raise PermissionException(data="", message="该用户无此接口权限")
class CheckRoleInterfaceAuth:
"""
根据角色校验当前用户是否具有相应的接口权限
:param role_key: 角色标识
:param is_strict: 当传入的角色标识是list类型时是否开启严格模式开启表示会校验列表中的每一个角色标识所有的校验结果都需要为True才会通过
"""
def __init__(self, role_key: Union[str, List], is_strict: bool = False):
self.role_key = role_key
self.is_strict = is_strict
def __call__(self, current_user: CurrentUserModel = Depends(LoginService.get_current_user)):
user_role_list = current_user.user.role
user_role_key_list = [role.role_key for role in user_role_list]
if isinstance(self.role_key, str):
if self.role_key in user_role_key_list:
return True
if isinstance(self.role_key, list):
if self.is_strict:
if all([role_key_str in user_role_key_list for role_key_str in self.role_key]):
return True
else:
if any([role_key_str in user_role_key_list for role_key_str in self.role_key]):
return True
raise PermissionException(data="", message="该用户无此接口权限")

View File

@@ -62,7 +62,7 @@ async def edit_system_config(request: Request, edit_config: ConfigModel, query_d
return ResponseUtil.error(msg=str(e)) return ResponseUtil.error(msg=str(e))
@configController.delete("/refreshCache", dependencies=[Depends(CheckUserInterfaceAuth('system:config:edit'))]) @configController.delete("/refreshCache", dependencies=[Depends(CheckUserInterfaceAuth('system:config:remove'))])
@log_decorator(title='参数管理', business_type=2) @log_decorator(title='参数管理', business_type=2)
async def refresh_system_config(request: Request, query_db: Session = Depends(get_db)): async def refresh_system_config(request: Request, query_db: Session = Depends(get_db)):
try: try:

View File

@@ -13,7 +13,7 @@ from module_admin.annotation.log_annotation import log_decorator
deptController = APIRouter(prefix='/system/dept', dependencies=[Depends(LoginService.get_current_user)]) deptController = APIRouter(prefix='/system/dept', dependencies=[Depends(LoginService.get_current_user)])
@deptController.get("/list/exclude/{dept_id}", response_model=List[DeptModel], dependencies=[Depends(CheckUserInterfaceAuth('common'))]) @deptController.get("/list/exclude/{dept_id}", response_model=List[DeptModel], dependencies=[Depends(CheckUserInterfaceAuth('system:dept:list'))])
async def get_system_dept_tree_for_edit_option(request: Request, dept_id: int, query_db: Session = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysDept'))): async def get_system_dept_tree_for_edit_option(request: Request, dept_id: int, query_db: Session = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysDept'))):
try: try:
dept_query = DeptModel(deptId=dept_id) dept_query = DeptModel(deptId=dept_id)

View File

@@ -62,7 +62,7 @@ async def edit_system_dict_type(request: Request, edit_dict_type: DictTypeModel,
return ResponseUtil.error(msg=str(e)) return ResponseUtil.error(msg=str(e))
@dictController.delete("/type/refreshCache", dependencies=[Depends(CheckUserInterfaceAuth('system:dict:edit'))]) @dictController.delete("/type/refreshCache", dependencies=[Depends(CheckUserInterfaceAuth('system:dict:remove'))])
@log_decorator(title='字典管理', business_type=2) @log_decorator(title='字典管理', business_type=2)
async def refresh_system_dict(request: Request, query_db: Session = Depends(get_db)): async def refresh_system_dict(request: Request, query_db: Session = Depends(get_db)):
try: try:
@@ -95,7 +95,7 @@ async def delete_system_dict_type(request: Request, dict_ids: str, query_db: Ses
return ResponseUtil.error(msg=str(e)) return ResponseUtil.error(msg=str(e))
@dictController.get("/type/optionselect", response_model=List[DictTypeModel], dependencies=[Depends(CheckUserInterfaceAuth('system:dict:query'))]) @dictController.get("/type/optionselect", response_model=List[DictTypeModel])
async def query_system_dict_type_options(request: Request, query_db: Session = Depends(get_db)): async def query_system_dict_type_options(request: Request, query_db: Session = Depends(get_db)):
try: try:
dict_type_query_result = DictTypeService.get_dict_type_list_services(query_db, DictTypePageQueryModel(**dict()), is_page=False) dict_type_query_result = DictTypeService.get_dict_type_list_services(query_db, DictTypePageQueryModel(**dict()), is_page=False)
@@ -131,7 +131,7 @@ async def export_system_dict_type_list(request: Request, dict_type_page_query: D
return ResponseUtil.error(msg=str(e)) return ResponseUtil.error(msg=str(e))
@dictController.get("/data/type/{dict_type}", dependencies=[Depends(CheckUserInterfaceAuth('system:dict:list'))]) @dictController.get("/data/type/{dict_type}")
async def query_system_dict_type_data(request: Request, dict_type: str, query_db: Session = Depends(get_db)): async def query_system_dict_type_data(request: Request, dict_type: str, query_db: Session = Depends(get_db)):
try: try:
# 获取全量数据 # 获取全量数据

View File

@@ -63,7 +63,7 @@ async def edit_system_job(request: Request, edit_job: EditJobModel, query_db: Se
return ResponseUtil.error(msg=str(e)) return ResponseUtil.error(msg=str(e))
@jobController.put("/job/changeStatus", dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:edit'))]) @jobController.put("/job/changeStatus", dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:changeStatus'))])
@log_decorator(title='定时任务管理', business_type=2) @log_decorator(title='定时任务管理', business_type=2)
async def edit_system_job(request: Request, edit_job: EditJobModel, query_db: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): async def edit_system_job(request: Request, edit_job: EditJobModel, query_db: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)):
try: try:

View File

@@ -70,7 +70,7 @@ async def export_system_operation_log_list(request: Request, operation_log_page_
return ResponseUtil.streaming(data=bytes2file_response(operation_log_export_result)) return ResponseUtil.streaming(data=bytes2file_response(operation_log_export_result))
except Exception as e: except Exception as e:
logger.exception(e) logger.exception(e)
return response_500(data="", message=str(e)) return ResponseUtil.error(msg=str(e))
@logController.get("/logininfor/list", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:list'))]) @logController.get("/logininfor/list", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:list'))])

View File

@@ -12,7 +12,7 @@ from module_admin.annotation.log_annotation import log_decorator
menuController = APIRouter(prefix='/system/menu', dependencies=[Depends(LoginService.get_current_user)]) menuController = APIRouter(prefix='/system/menu', dependencies=[Depends(LoginService.get_current_user)])
@menuController.get("/treeselect", dependencies=[Depends(CheckUserInterfaceAuth('common'))]) @menuController.get("/treeselect")
async def get_system_menu_tree(request: Request, query_db: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): async def get_system_menu_tree(request: Request, query_db: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)):
try: try:
menu_query_result = MenuService.get_menu_tree_services(query_db, current_user) menu_query_result = MenuService.get_menu_tree_services(query_db, current_user)
@@ -23,7 +23,7 @@ async def get_system_menu_tree(request: Request, query_db: Session = Depends(get
return ResponseUtil.error(msg=str(e)) return ResponseUtil.error(msg=str(e))
@menuController.get("/roleMenuTreeselect/{role_id}", dependencies=[Depends(CheckUserInterfaceAuth('common'))]) @menuController.get("/roleMenuTreeselect/{role_id}")
async def get_system_role_menu_tree(request: Request, role_id: int, query_db: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): async def get_system_role_menu_tree(request: Request, role_id: int, query_db: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)):
try: try:
role_menu_query_result = MenuService.get_role_menu_tree_services(query_db, role_id, current_user) role_menu_query_result = MenuService.get_role_menu_tree_services(query_db, role_id, current_user)

View File

@@ -17,7 +17,7 @@ from module_admin.annotation.log_annotation import log_decorator
roleController = APIRouter(prefix='/system/role', dependencies=[Depends(LoginService.get_current_user)]) roleController = APIRouter(prefix='/system/role', dependencies=[Depends(LoginService.get_current_user)])
@roleController.get("/deptTree/{role_id}", dependencies=[Depends(CheckUserInterfaceAuth('common'))]) @roleController.get("/deptTree/{role_id}", dependencies=[Depends(CheckUserInterfaceAuth('system:role:query'))])
async def get_system_role_dept_tree(request: Request, role_id: int, query_db: Session = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysDept'))): async def get_system_role_dept_tree(request: Request, role_id: int, query_db: Session = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysDept'))):
try: try:
dept_query_result = DeptService.get_dept_tree_services(query_db, DeptModel(**{}), data_scope_sql) dept_query_result = DeptService.get_dept_tree_services(query_db, DeptModel(**{}), data_scope_sql)
@@ -160,7 +160,7 @@ async def reset_system_role_status(request: Request, edit_role: AddRoleModel, qu
return ResponseUtil.error(msg=str(e)) return ResponseUtil.error(msg=str(e))
@roleController.get("/authUser/allocatedList", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('common'))]) @roleController.get("/authUser/allocatedList", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:role:list'))])
async def get_system_allocated_user_list(request: Request, user_role: UserRolePageQueryModel = Depends(UserRolePageQueryModel.as_query), query_db: Session = Depends(get_db)): async def get_system_allocated_user_list(request: Request, user_role: UserRolePageQueryModel = Depends(UserRolePageQueryModel.as_query), query_db: Session = Depends(get_db)):
try: try:
role_user_allocated_page_query_result = RoleService.get_role_user_allocated_list_services(query_db, user_role, is_page=True) role_user_allocated_page_query_result = RoleService.get_role_user_allocated_list_services(query_db, user_role, is_page=True)
@@ -171,7 +171,7 @@ async def get_system_allocated_user_list(request: Request, user_role: UserRolePa
return ResponseUtil.error(msg=str(e)) return ResponseUtil.error(msg=str(e))
@roleController.get("/authUser/unallocatedList", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('common'))]) @roleController.get("/authUser/unallocatedList", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:role:list'))])
async def get_system_unallocated_user_list(request: Request, user_role: UserRolePageQueryModel = Depends(UserRolePageQueryModel.as_query), query_db: Session = Depends(get_db)): async def get_system_unallocated_user_list(request: Request, user_role: UserRolePageQueryModel = Depends(UserRolePageQueryModel.as_query), query_db: Session = Depends(get_db)):
try: try:
role_user_unallocated_page_query_result = RoleService.get_role_user_unallocated_list_services(query_db, user_role, is_page=True) role_user_unallocated_page_query_result = RoleService.get_role_user_unallocated_list_services(query_db, user_role, is_page=True)

View File

@@ -1,5 +1,6 @@
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from module_admin.entity.do.dept_do import SysDept from module_admin.entity.do.dept_do import SysDept
from module_admin.entity.do.role_do import SysRoleDept
from module_admin.entity.vo.dept_vo import * from module_admin.entity.vo.dept_vo import *
from utils.time_format_util import list_format_datetime from utils.time_format_util import list_format_datetime

View File

@@ -1,7 +1,9 @@
from sqlalchemy import asc, desc
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from module_admin.entity.do.log_do import SysOperLog, SysLogininfor from module_admin.entity.do.log_do import SysOperLog, SysLogininfor
from module_admin.entity.vo.log_vo import * from module_admin.entity.vo.log_vo import *
from utils.page_util import PageUtil from utils.page_util import PageUtil
from utils.common_util import CamelCaseUtil
from datetime import datetime, time from datetime import datetime, time
@@ -18,6 +20,12 @@ class OperationLogDao:
:param is_page: 是否开启分页 :param is_page: 是否开启分页
:return: 操作日志列表信息对象 :return: 操作日志列表信息对象
""" """
if query_object.is_asc == 'ascending':
order_by_column = asc(getattr(SysOperLog, CamelCaseUtil.camel_to_snake(query_object.order_by_column), None))
elif query_object.is_asc == 'descending':
order_by_column = desc(getattr(SysOperLog, CamelCaseUtil.camel_to_snake(query_object.order_by_column), None))
else:
order_by_column = desc(SysOperLog.oper_time)
query = db.query(SysOperLog) \ query = db.query(SysOperLog) \
.filter(SysOperLog.title.like(f'%{query_object.title}%') if query_object.title else True, .filter(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.oper_name.like(f'%{query_object.oper_name}%') if query_object.oper_name else True,
@@ -28,7 +36,7 @@ class OperationLogDao:
datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59))) 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 if query_object.begin_time and query_object.end_time else True
)\ )\
.distinct() .distinct().order_by(order_by_column)
operation_log_list = PageUtil.paginate(query, query_object.page_num, query_object.page_size, is_page) operation_log_list = PageUtil.paginate(query, query_object.page_num, query_object.page_size, is_page)
return operation_log_list return operation_log_list
@@ -84,6 +92,12 @@ class LoginLogDao:
:param is_page: 是否开启分页 :param is_page: 是否开启分页
:return: 登录日志列表信息对象 :return: 登录日志列表信息对象
""" """
if query_object.is_asc == 'ascending':
order_by_column = asc(getattr(SysLogininfor, CamelCaseUtil.camel_to_snake(query_object.order_by_column), None))
elif query_object.is_asc == 'descending':
order_by_column = desc(getattr(SysLogininfor, CamelCaseUtil.camel_to_snake(query_object.order_by_column), None))
else:
order_by_column = desc(SysLogininfor.login_time)
query = db.query(SysLogininfor) \ query = db.query(SysLogininfor) \
.filter(SysLogininfor.ipaddr.like(f'%{query_object.ipaddr}%') if query_object.ipaddr else True, .filter(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.user_name.like(f'%{query_object.user_name}%') if query_object.user_name else True,
@@ -93,7 +107,7 @@ class LoginLogDao:
datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59))) 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 if query_object.begin_time and query_object.end_time else True
)\ )\
.distinct() .distinct().order_by(order_by_column)
login_log_list = PageUtil.paginate(query, query_object.page_num, query_object.page_size, is_page) login_log_list = PageUtil.paginate(query, query_object.page_num, query_object.page_size, is_page)
return login_log_list return login_log_list

View File

@@ -1,7 +1,7 @@
from sqlalchemy import and_, or_, desc, func from sqlalchemy import and_, or_, desc, func
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from module_admin.entity.do.user_do import SysUser, SysUserRole, SysUserPost from module_admin.entity.do.user_do import SysUser, SysUserRole, SysUserPost
from module_admin.entity.do.role_do import SysRole, SysRoleMenu from module_admin.entity.do.role_do import SysRole, SysRoleDept, SysRoleMenu
from module_admin.entity.do.dept_do import SysDept from module_admin.entity.do.dept_do import SysDept
from module_admin.entity.do.post_do import SysPost from module_admin.entity.do.post_do import SysPost
from module_admin.entity.do.menu_do import SysMenu from module_admin.entity.do.menu_do import SysMenu

View File

@@ -51,6 +51,8 @@ class OperLogQueryModel(OperLogModel):
""" """
操作日志管理不分页查询模型 操作日志管理不分页查询模型
""" """
order_by_column: Optional[str] = None
is_asc: Optional[str] = None
begin_time: Optional[str] = None begin_time: Optional[str] = None
end_time: Optional[str] = None end_time: Optional[str] = None
@@ -78,6 +80,8 @@ class LoginLogQueryModel(LogininforModel):
""" """
登录日志管理不分页查询模型 登录日志管理不分页查询模型
""" """
order_by_column: Optional[str] = None
is_asc: Optional[str] = None
begin_time: Optional[str] = None begin_time: Optional[str] = None
end_time: Optional[str] = None end_time: Optional[str] = None

View File

@@ -56,6 +56,7 @@ class LoginService:
:param login_user: 登录用户对象 :param login_user: 登录用户对象
:return: 校验结果 :return: 校验结果
""" """
await cls.__check_login_ip(request)
account_lock = await request.app.state.redis.get( account_lock = await request.app.state.redis.get(
f"{RedisInitKeyConfig.ACCOUNT_LOCK.get('key')}:{login_user.user_name}") f"{RedisInitKeyConfig.ACCOUNT_LOCK.get('key')}:{login_user.user_name}")
if login_user.user_name == account_lock: if login_user.user_name == account_lock:
@@ -100,6 +101,21 @@ class LoginService:
f"{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.get('key')}:{login_user.user_name}") f"{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.get('key')}:{login_user.user_name}")
return user return user
@classmethod
async def __check_login_ip(cls, request: Request):
"""
校验用户登录ip是否在黑名单内
:param request: Request对象
:return: 校验结果
"""
black_ip_value = await request.app.state.redis.get(
f"{RedisInitKeyConfig.SYS_CONFIG.get('key')}:sys.login.blackIPList")
black_ip_list = black_ip_value.split(',') if black_ip_value else []
if request.headers.get('X-Forwarded-For') in black_ip_list:
logger.warning("当前IP禁止登录")
raise LoginException(data="", message="当前IP禁止登录")
return True
@classmethod @classmethod
async def __check_login_captcha(cls, request: Request, login_user: UserLogin): async def __check_login_captcha(cls, request: Request, login_user: UserLogin):
""" """
@@ -229,14 +245,16 @@ class LoginService:
if permission.menu_type == 'M': if permission.menu_type == 'M':
router_list_data['name'] = permission.path.capitalize() router_list_data['name'] = permission.path.capitalize()
router_list_data['hidden'] = False if permission.visible == '0' else True router_list_data['hidden'] = False if permission.visible == '0' else True
if permission.is_frame == 1:
router_list_data['redirect'] = 'noRedirect'
if permission.parent_id == 0: if permission.parent_id == 0:
router_list_data['component'] = 'Layout' router_list_data['component'] = 'Layout'
router_list_data['path'] = f'/{permission.path}' router_list_data['path'] = f'/{permission.path}'
else: else:
router_list_data['component'] = 'ParentView' router_list_data['component'] = 'ParentView'
router_list_data['path'] = permission.path router_list_data['path'] = permission.path
if permission.is_frame == 1:
router_list_data['redirect'] = 'noRedirect'
else:
router_list_data['path'] = permission.path
if children: if children:
router_list_data['alwaysShow'] = True router_list_data['alwaysShow'] = True
router_list_data['children'] = children router_list_data['children'] = children

View File

@@ -1,6 +1,6 @@
APScheduler==3.10.4 APScheduler==3.10.4
DateTime==5.4 DateTime==5.4
fastapi[all]==0.109.0 fastapi[all]==0.109.1
loguru==0.7.2 loguru==0.7.2
openpyxl==3.1.2 openpyxl==3.1.2
pandas==2.1.4 pandas==2.1.4

View File

@@ -1,6 +1,7 @@
import pandas as pd import pandas as pd
import io import io
import os import os
import re
from openpyxl import Workbook from openpyxl import Workbook
from openpyxl.styles import Alignment, PatternFill from openpyxl.styles import Alignment, PatternFill
from openpyxl.utils import get_column_letter from openpyxl.utils import get_column_letter
@@ -39,10 +40,21 @@ def worship():
class CamelCaseUtil: class CamelCaseUtil:
""" """
下划线形式(snake_case)转换为小驼峰形式(camelCase)工具方法 小驼峰形式(camelCase)与下划线形式(snake_case)互相转换工具方法
""" """
@classmethod @classmethod
def __to_camel_case(cls, snake_str): def camel_to_snake(cls, camel_str):
"""
小驼峰形式字符串(camelCase)转换为下划线形式字符串(snake_case)
:param camel_str: 小驼峰形式字符串
:return: 下划线形式字符串
"""
# 在大写字母前添加一个下划线,然后将整个字符串转为小写
words = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', camel_str)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', words).lower()
@classmethod
def snake_to_camel(cls, snake_str):
""" """
下划线形式字符串(snake_case)转换为小驼峰形式字符串(camelCase) 下划线形式字符串(snake_case)转换为小驼峰形式字符串(camelCase)
:param snake_str: 下划线形式字符串 :param snake_str: 下划线形式字符串
@@ -64,7 +76,7 @@ class CamelCaseUtil:
return result return result
# 如果是字典,直接转换键 # 如果是字典,直接转换键
elif isinstance(result, dict): elif isinstance(result, dict):
return {cls.__to_camel_case(k): v for k, v in result.items()} return {cls.snake_to_camel(k): v for k, v in result.items()}
# 如果是一组字典或其他类型的列表,遍历列表进行转换 # 如果是一组字典或其他类型的列表,遍历列表进行转换
elif isinstance(result, list): elif isinstance(result, list):
return [cls.transform_result(row) if isinstance(row, (dict, Row)) else (cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row) for row in result] return [cls.transform_result(row) if isinstance(row, (dict, Row)) else (cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row) for row in result]

View File

@@ -1,109 +0,0 @@
<p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.8.7</h1>
<h4 align="center">基于SpringBoot+Vue3前后端分离的Java快速开发框架</h4>
<p align="center">
<a href="https://gitee.com/y_project/RuoYi-Vue/stargazers"><img src="https://gitee.com/y_project/RuoYi-Vue/badge/star.svg?theme=dark"></a>
<a href="https://gitee.com/y_project/RuoYi-Vue"><img src="https://img.shields.io/badge/RuoYi-v3.8.7-brightgreen.svg"></a>
<a href="https://gitee.com/y_project/RuoYi-Vue/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
</p>
## 平台简介
* 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本。
* 配套后端代码仓库地址[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue) 或 [RuoYi-Vue-fast](https://github.com/yangzongzhuan/RuoYi-Vue-fast) 版本。
* 前端技术栈([Vue2](https://cn.vuejs.org) + [Element](https://github.com/ElemeFE/element) + [Vue CLI](https://cli.vuejs.org/zh)),请移步[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue/tree/master/ruoyi-ui)。
* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)&nbsp;&nbsp;
* 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)&nbsp;&nbsp;
## 前端运行
```bash
# 克隆项目
git clone https://github.com/yangzongzhuan/RuoYi-Vue3.git
# 进入项目目录
cd RuoYi-Vue3
# 安装依赖
yarn --registry=https://registry.npmmirror.com
# 启动服务
yarn dev
# 构建测试环境 yarn build:stage
# 构建生产环境 yarn build:prod
# 前端访问地址 http://localhost:80
```
## 内置功能
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
3. 岗位管理:配置系统用户所属担任职务。
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
7. 参数管理:对系统动态配置常用参数。
8. 通知公告:系统通知公告信息发布维护。
9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
10. 登录日志:系统登录日志记录查询包含登录异常。
11. 在线用户:当前系统中活跃用户状态监控。
12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
13. 代码生成前后端代码的生成java、html、xml、sql支持CRUD下载 。
14. 系统接口根据业务代码自动生成相关的api接口文档。
15. 服务监控监视当前系统CPU、内存、磁盘、堆栈等相关信息。
16. 缓存监控:对系统的缓存信息查询,命令统计等。
17. 在线构建器拖动表单元素生成相应的HTML代码。
18. 连接池监视监视当前系统数据库连接池状态可进行分析SQL找出系统性能瓶颈。
## 在线体验
- admin/admin123
- 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。
演示地址http://vue.ruoyi.vip
文档地址http://doc.ruoyi.vip
## 演示图
<table>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/cd1f90be5f2684f4560c9519c0f2a232ee8.jpg"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/1cbcf0e6f257c7d3a063c0e3f2ff989e4b3.jpg"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-8074972883b5ba0622e13246738ebba237a.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-9f88719cdfca9af2e58b352a20e23d43b12.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-39bf2584ec3a529b0d5a3b70d15c9b37646.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-936ec82d1f4872e1bc980927654b6007307.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-b2d62ceb95d2dd9b3fbe157bb70d26001e9.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-d67451d308b7a79ad6819723396f7c3d77a.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/5e8c387724954459291aafd5eb52b456f53.jpg"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/644e78da53c2e92a95dfda4f76e6d117c4b.jpg"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-8370a0d02977eebf6dbf854c8450293c937.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-49003ed83f60f633e7153609a53a2b644f7.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-d4fe726319ece268d4746602c39cffc0621.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-c195234bbcd30be6927f037a6755e6ab69c.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/b6115bc8c31de52951982e509930b20684a.jpg"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-5e4daac0bb59612c5038448acbcef235e3a.png"/></td>
</tr>
</table>
## 若依前后端分离交流群
QQ群 [![加入QQ群](https://img.shields.io/badge/已满-937441-blue.svg)](https://jq.qq.com/?_wv=1027&k=5bVB1og) [![加入QQ群](https://img.shields.io/badge/已满-887144332-blue.svg)](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [![加入QQ群](https://img.shields.io/badge/已满-180251782-blue.svg)](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [![加入QQ群](https://img.shields.io/badge/已满-104180207-blue.svg)](https://jq.qq.com/?_wv=1027&k=51G72yr) [![加入QQ群](https://img.shields.io/badge/已满-186866453-blue.svg)](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [![加入QQ群](https://img.shields.io/badge/已满-201396349-blue.svg)](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [![加入QQ群](https://img.shields.io/badge/已满-101456076-blue.svg)](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [![加入QQ群](https://img.shields.io/badge/已满-101539465-blue.svg)](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [![加入QQ群](https://img.shields.io/badge/已满-264312783-blue.svg)](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [![加入QQ群](https://img.shields.io/badge/已满-167385320-blue.svg)](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) [![加入QQ群](https://img.shields.io/badge/已满-104748341-blue.svg)](https://jq.qq.com/?_wv=1027&k=96Dkdq0k) [![加入QQ群](https://img.shields.io/badge/已满-160110482-blue.svg)](https://jq.qq.com/?_wv=1027&k=0fsNiYZt) [![加入QQ群](https://img.shields.io/badge/已满-170801498-blue.svg)](https://jq.qq.com/?_wv=1027&k=7xw4xUG1) [![加入QQ群](https://img.shields.io/badge/已满-108482800-blue.svg)](https://jq.qq.com/?_wv=1027&k=eCx8eyoJ) [![加入QQ群](https://img.shields.io/badge/已满-101046199-blue.svg)](https://jq.qq.com/?_wv=1027&k=SpyH2875) [![加入QQ群](https://img.shields.io/badge/已满-136919097-blue.svg)](https://jq.qq.com/?_wv=1027&k=tKEt51dz) [![加入QQ群](https://img.shields.io/badge/已满-143961921-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0vBbSb0ztbBgVtn3kJS-Q4HUNYwip89G&authKey=8irq5PhutrZmWIvsUsklBxhj57l%2F1nOZqjzigkXZVoZE451GG4JHPOqW7AW6cf0T&noverify=0&group_code=143961921) [![加入QQ群](https://img.shields.io/badge/已满-174951577-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ZFAPAbp09S2ltvwrJzp7wGlbopsc0rwi&authKey=HB2cxpxP2yspk%2Bo3WKTBfktRCccVkU26cgi5B16u0KcAYrVu7sBaE7XSEqmMdFQp&noverify=0&group_code=174951577) [![加入QQ群](https://img.shields.io/badge/161281055-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Fn2aF5IHpwsy8j6VlalNJK6qbwFLFHat&authKey=uyIT%2B97x2AXj3odyXpsSpVaPMC%2Bidw0LxG5MAtEqlrcBcWJUA%2FeS43rsF1Tg7IRJ&noverify=0&group_code=161281055) 点击按钮入群。

View File

@@ -1,6 +1,6 @@
{ {
"name": "vfadmin", "name": "vfadmin",
"version": "1.0.0", "version": "1.0.3",
"description": "vfadmin管理系统", "description": "vfadmin管理系统",
"author": "insistence", "author": "insistence",
"license": "MIT", "license": "MIT",

View File

@@ -152,8 +152,8 @@
</span> </span>
</template> </template>
<el-radio-group v-model="form.isFrame"> <el-radio-group v-model="form.isFrame">
<el-radio label="0"></el-radio> <el-radio :label="0"></el-radio>
<el-radio label="1"></el-radio> <el-radio :label="1"></el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -220,8 +220,8 @@
</span> </span>
</template> </template>
<el-radio-group v-model="form.isCache"> <el-radio-group v-model="form.isCache">
<el-radio label="0">缓存</el-radio> <el-radio :label="0">缓存</el-radio>
<el-radio label="1">不缓存</el-radio> <el-radio :label="1">不缓存</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col> </el-col>