54 Commits

Author SHA1 Message Date
insistence
18103e3d38 !10 RuoYi-Vue3-FastAPI v1.1.3
Merge pull request !10 from insistence/develop
2024-05-13 01:20:40 +00:00
insistence
cb96c878bf docs: 更新README文档 2024-05-13 09:16:04 +08:00
insistence
266b3e3b5c chore: 升级版本至1.1.3 2024-05-13 09:15:01 +08:00
insistence
6ea8ada989 fix: 修复个人中心修改基本资料后端异常的问题 2024-05-11 21:15:14 +08:00
insistence
901a66bafa feat: 用户密码新增非法字符验证 2024-05-11 14:58:02 +08:00
insistence
c9cb8c6542 fix: 修复通知公告列表查询前后端字段不一致的问题 2024-05-11 09:05:07 +08:00
insistence
34f9e891b6 !9 RuoYi-Vue3-FastAPI v1.1.2
Merge pull request !9 from insistence/develop
2024-04-29 01:07:48 +00:00
insistence
fb9dfa4674 docs: 更新README文档 2024-04-29 08:52:12 +08:00
insistence
bbb7214cee chore: 升级版本至1.1.2 2024-04-29 08:43:57 +08:00
insistence
52e92d50d1 perf: 使用@lru_cache缓存ip归属区域查询结果,避免重复调用ip归属区域查询接口以优化性能 2024-04-24 10:03:52 +08:00
insistence
816793b888 feat: 配置文件新增数据库连接池相关配置 2024-04-23 09:04:49 +08:00
insistence
e774e1b26b fix: 修复个人中心修改密码后端异常的问题 2024-04-20 12:55:43 +08:00
insistence
42009cf3f4 !8 RuoYi-Vue3-FastAPI v1.1.1
Merge pull request !8 from insistence/develop
2024-04-19 03:40:20 +00:00
insistence
f4afa20ac2 docs: 更新README文档 2024-04-19 11:39:04 +08:00
insistence
7fd3109b52 chore: 升级版本至1.1.1 2024-04-19 11:38:12 +08:00
insistence
dfb8af23b3 fix: 修复添加菜单时是否外链和是否缓存回显异常的问题 2024-04-19 11:31:24 +08:00
insistence
b423647ad5 fix: 修复获取路由信息时菜单排序不生效的问题 2024-04-17 16:03:49 +08:00
insistence
423491302d fix: 修复菜单配置路由参数不生效的问题 2024-04-17 10:51:16 +08:00
insistence
fa27fd3b68 fix: 修复编辑角色数据权限时后端异常的问题 #I9ENQN 2024-04-08 11:04:32 +08:00
insistence
88b27685c1 fix: 修复编辑定时任务时更新的信息未同步至scheduler的问题 #I9EK56 2024-04-08 11:02:41 +08:00
insistence
2bade4d6c9 !7 RuoYi-Vue3-FastAPI v1.1.0
Merge pull request !7 from insistence/develop
2024-04-02 03:08:22 +00:00
insistence
a06c9f17d6 docs: 更新README文档 2024-04-02 10:47:48 +08:00
insistence
3654f4d88b chore: 升级版本至1.1.0 2024-04-02 10:45:44 +08:00
insistence
38aca38d4d feat: 后端配置文件新增账号同时登录开关配置 2024-04-02 10:42:07 +08:00
insistence
a57d737261 feat: 后端配置文件新增IP归属区域查询开关配置 2024-04-02 09:52:07 +08:00
insistence
84f56da523 feat: 后端配置文件新增sqlalchemy日志开关配置 2024-04-02 09:50:38 +08:00
insistence
f73a00e73c fix: 修复系统版本号或浏览器版本号无法获取时登录异常的问题 #I9CYNM 2024-04-01 09:56:22 +08:00
insistence
a84ad47de4 fix: 修复token本身过期时退出登录接口异常的问题 #I9CBWT 2024-03-29 11:08:00 +08:00
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
41 changed files with 413 additions and 295 deletions

View File

@@ -1,20 +1,27 @@
<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.1.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.1.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-Vue3-FastAPI是一套全部开源的快速开发平台毫无保留给个人及企业免费使用。
* 前端采用Vue、Element Plus基于<u>[RuoYi-Vue3](https://github.com/yangzongzhuan/RuoYi-Vue3)</u>前端项目修改。 * 前端采用Vue、Element Plus基于<u>[RuoYi-Vue3](https://github.com/yangzongzhuan/RuoYi-Vue3)</u>前端项目修改。
* 后端采用FastAPI、sqlalchemy、MySQL、Redis、OAuth2 & Jwt。 * 后端采用FastAPI、sqlalchemy、MySQL、Redis、OAuth2 & Jwt。

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,9 +10,13 @@ APP_HOST = '0.0.0.0'
# 应用端口 # 应用端口
APP_PORT = 9099 APP_PORT = 9099
# 应用版本 # 应用版本
APP_VERSION= '1.0.0' APP_VERSION= '1.1.3'
# 应用是否开启热重载 # 应用是否开启热重载
APP_RELOAD = true APP_RELOAD = true
# 应用是否开启IP归属区域查询
APP_IP_LOCATION_QUERY = true
# 应用是否允许账号同时登录
APP_SAME_TIME_LOGIN = true
# -------- Jwt配置 -------- # -------- Jwt配置 --------
# Jwt秘钥 # Jwt秘钥
@@ -36,6 +40,16 @@ DB_USERNAME = 'root'
DB_PASSWORD = 'mysqlroot' DB_PASSWORD = 'mysqlroot'
# 数据库名称 # 数据库名称
DB_DATABASE = 'ruoyi-fastapi' DB_DATABASE = 'ruoyi-fastapi'
# 是否开启sqlalchemy日志
DB_ECHO = true
# 允许溢出连接池大小的最大连接数
DB_MAX_OVERFLOW = 10
# 连接池大小0表示连接数无限制
DB_POOL_SIZE = 50
# 连接回收时间(单位:秒)
DB_POOL_RECYCLE = 3600
# 连接池中没有线程可用时,最多等待的时间(单位:秒)
DB_POOL_TIMEOUT = 30
# -------- Redis配置 -------- # -------- Redis配置 --------
# Redis主机 # Redis主机

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,9 +10,13 @@ APP_HOST = '0.0.0.0'
# 应用端口 # 应用端口
APP_PORT = 9099 APP_PORT = 9099
# 应用版本 # 应用版本
APP_VERSION= '1.0.0' APP_VERSION= '1.1.3'
# 应用是否开启热重载 # 应用是否开启热重载
APP_RELOAD = false APP_RELOAD = false
# 应用是否开启IP归属区域查询
APP_IP_LOCATION_QUERY = true
# 应用是否允许账号同时登录
APP_SAMETIME_LOGIN = true
# -------- Jwt配置 -------- # -------- Jwt配置 --------
# Jwt秘钥 # Jwt秘钥
@@ -36,6 +40,16 @@ DB_USERNAME = 'root'
DB_PASSWORD = 'root' DB_PASSWORD = 'root'
# 数据库名称 # 数据库名称
DB_DATABASE = 'ruoyi-fastapi' DB_DATABASE = 'ruoyi-fastapi'
# 是否开启sqlalchemy日志
DB_ECHO = true
# 允许溢出连接池大小的最大连接数
DB_MAX_OVERFLOW = 10
# 连接池大小0表示连接数无限制
DB_POOL_SIZE = 50
# 连接回收时间(单位:秒)
DB_POOL_RECYCLE = 3600
# 连接池中没有线程可用时,最多等待的时间(单位:秒)
DB_POOL_TIMEOUT = 30
# -------- Redis配置 -------- # -------- Redis配置 --------
# Redis主机 # Redis主机

View File

@@ -8,7 +8,12 @@ SQLALCHEMY_DATABASE_URL = f"mysql+pymysql://{DataBaseConfig.db_username}:{quote_
f"{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}" f"{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}"
engine = create_engine( engine = create_engine(
SQLALCHEMY_DATABASE_URL, echo=True SQLALCHEMY_DATABASE_URL,
echo=DataBaseConfig.db_echo,
max_overflow=DataBaseConfig.db_max_overflow,
pool_size=DataBaseConfig.db_pool_size,
pool_recycle=DataBaseConfig.db_pool_recycle,
pool_timeout=DataBaseConfig.db_pool_timeout
) )
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base() Base = declarative_base()

View File

@@ -17,6 +17,8 @@ class AppSettings(BaseSettings):
app_port: int = 9099 app_port: int = 9099
app_version: str = '1.0.0' app_version: str = '1.0.0'
app_reload: bool = True app_reload: bool = True
app_ip_location_query: bool = True
app_same_time_login: bool = True
class JwtSettings(BaseSettings): class JwtSettings(BaseSettings):
@@ -38,6 +40,11 @@ class DataBaseSettings(BaseSettings):
db_username: str = 'root' db_username: str = 'root'
db_password: str = 'mysqlroot' db_password: str = 'mysqlroot'
db_database: str = 'ruoyi-fastapi' db_database: str = 'ruoyi-fastapi'
db_echo: bool = True
db_max_overflow: int = 10
db_pool_size: int = 50
db_pool_recycle: int = 3600
db_pool_timeout: int = 30
class RedisSettings(BaseSettings): class RedisSettings(BaseSettings):

View File

@@ -26,3 +26,13 @@ class PermissionException(Exception):
def __init__(self, data: str = None, message: str = None): def __init__(self, data: str = None, message: str = None):
self.data = data self.data = data
self.message = message self.message = message
class ModelValidatorException(Exception):
"""
自定义模型校验异常ModelValidatorException
"""
def __init__(self, data: str = None, message: str = None):
self.data = data
self.message = message

View File

@@ -1,6 +1,6 @@
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.exceptions import HTTPException from fastapi.exceptions import HTTPException
from exceptions.exception import AuthException, PermissionException from exceptions.exception import AuthException, PermissionException, ModelValidatorException
from utils.response_util import ResponseUtil, JSONResponse, jsonable_encoder from utils.response_util import ResponseUtil, JSONResponse, jsonable_encoder
@@ -18,6 +18,11 @@ def handle_exception(app: FastAPI):
async def permission_exception_handler(request: Request, exc: PermissionException): async def permission_exception_handler(request: Request, exc: PermissionException):
return ResponseUtil.forbidden(data=exc.data, msg=exc.message) return ResponseUtil.forbidden(data=exc.data, msg=exc.message)
# 自定义模型检验异常
@app.exception_handler(ModelValidatorException)
async def model_validator_exception_handler(request: Request, exc: ModelValidatorException):
return ResponseUtil.failure(data=exc.data, msg=exc.message)
# 处理其他http请求异常 # 处理其他http请求异常
@app.exception_handler(HTTPException) @app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException): async def http_exception_handler(request: Request, exc: HTTPException):

View File

@@ -1,4 +1,4 @@
from functools import wraps from functools import wraps, lru_cache
from fastapi import Request from fastapi import Request
from fastapi.responses import JSONResponse, ORJSONResponse, UJSONResponse from fastapi.responses import JSONResponse, ORJSONResponse, UJSONResponse
import inspect import inspect
@@ -12,6 +12,7 @@ from typing import Optional
from module_admin.service.login_service import LoginService from module_admin.service.login_service import LoginService
from module_admin.service.log_service import OperationLogService, LoginLogService from module_admin.service.log_service import OperationLogService, LoginLogService
from module_admin.entity.vo.log_vo import OperLogModel, LogininforModel from module_admin.entity.vo.log_vo import OperLogModel, LogininforModel
from config.env import AppConfig
def log_decorator(title: str, business_type: int, log_type: Optional[str] = 'operation'): def log_decorator(title: str, business_type: int, log_type: Optional[str] = 'operation'):
@@ -50,22 +51,8 @@ def log_decorator(title: str, business_type: int, log_type: Optional[str] = 'ope
# 获取请求的ip及ip归属区域 # 获取请求的ip及ip归属区域
oper_ip = request.headers.get("X-Forwarded-For") oper_ip = request.headers.get("X-Forwarded-For")
oper_location = '内网IP' oper_location = '内网IP'
try: if AppConfig.app_ip_location_query:
if oper_ip != '127.0.0.1' and oper_ip != 'localhost': oper_location = get_ip_location(oper_ip)
ip_result = requests.get(f'https://qifu-api.baidubce.com/ip/geo/v1/district?ip={oper_ip}')
if ip_result.status_code == 200:
prov = ip_result.json().get('data').get('prov')
city = ip_result.json().get('data').get('city')
if prov or city:
oper_location = f'{prov}-{city}'
else:
oper_location = '未知'
else:
oper_location = '未知'
except Exception as e:
oper_location = '未知'
print(e)
finally:
# 根据不同的请求类型使用不同的方法获取请求参数 # 根据不同的请求类型使用不同的方法获取请求参数
content_type = request.headers.get("Content-Type") content_type = request.headers.get("Content-Type")
if content_type and ("multipart/form-data" in content_type or 'application/x-www-form-urlencoded' in content_type): if content_type and ("multipart/form-data" in content_type or 'application/x-www-form-urlencoded' in content_type):
@@ -91,8 +78,12 @@ def log_decorator(title: str, business_type: int, log_type: Optional[str] = 'ope
login_log = {} login_log = {}
if log_type == 'login': if log_type == 'login':
user_agent_info = parse(user_agent) user_agent_info = parse(user_agent)
browser = f'{user_agent_info.browser.family} {user_agent_info.browser.version[0]}' browser = f'{user_agent_info.browser.family}'
system_os = f'{user_agent_info.os.family} {user_agent_info.os.version[0]}' system_os = f'{user_agent_info.os.family}'
if user_agent_info.browser.version != ():
browser += f' {user_agent_info.browser.version[0]}'
if user_agent_info.os.version != ():
system_os += f' {user_agent_info.os.version[0]}'
login_log = dict( login_log = dict(
ipaddr=oper_ip, ipaddr=oper_ip,
loginLocation=oper_location, loginLocation=oper_location,
@@ -170,3 +161,26 @@ def log_decorator(title: str, business_type: int, log_type: Optional[str] = 'ope
return wrapper return wrapper
return decorator return decorator
@lru_cache()
def get_ip_location(oper_ip: str):
"""
查询ip归属区域
:param oper_ip: 需要查询的ip
:return: ip归属区域
"""
oper_location = '内网IP'
try:
if oper_ip != '127.0.0.1' and oper_ip != 'localhost':
oper_location = '未知'
ip_result = requests.get(f'https://qifu-api.baidubce.com/ip/geo/v1/district?ip={oper_ip}')
if ip_result.status_code == 200:
prov = ip_result.json().get('data').get('prov')
city = ip_result.json().get('data').get('city')
if prov or city:
oper_location = f'{prov}-{city}'
except Exception as e:
oper_location = '未知'
print(e)
return oper_location

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

@@ -41,11 +41,13 @@ async def login(request: Request, form_data: CustomOAuth2PasswordRequestForm = D
}, },
expires_delta=access_token_expires expires_delta=access_token_expires
) )
if AppConfig.app_same_time_login:
await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{session_id}", access_token, await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{session_id}", access_token,
ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes)) ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes))
else:
# 此方法可实现同一账号同一时间只能登录一次 # 此方法可实现同一账号同一时间只能登录一次
# await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{result[0].user_id}", access_token, await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{result[0].user_id}", access_token,
# ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes)) ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes))
UserService.edit_user_services(query_db, EditUserModel(userId=result[0].user_id, loginDate=datetime.now(), type='status')) UserService.edit_user_services(query_db, EditUserModel(userId=result[0].user_id, loginDate=datetime.now(), type='status'))
logger.info('登录成功') logger.info('登录成功')
# 判断请求是否来自于api文档如果是返回指定格式的结果用于修复api文档认证成功后token显示undefined的bug # 判断请求是否来自于api文档如果是返回指定格式的结果用于修复api文档认证成功后token显示undefined的bug
@@ -131,7 +133,7 @@ async def register_user(request: Request, user_register: UserRegister, query_db:
@loginController.post("/logout") @loginController.post("/logout")
async def logout(request: Request, token: Optional[str] = Depends(oauth2_scheme)): async def logout(request: Request, token: Optional[str] = Depends(oauth2_scheme)):
try: try:
payload = jwt.decode(token, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm]) payload = jwt.decode(token, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm], options={'verify_exp': False})
session_id: str = payload.get("session_id") session_id: str = payload.get("session_id")
await LoginService.logout_services(request, session_id) await LoginService.logout_services(request, session_id)
logger.info('退出成功') logger.info('退出成功')

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

@@ -198,11 +198,20 @@ async def change_system_user_profile_avatar(request: Request, avatarfile: bytes
@log_decorator(title='个人信息', business_type=2) @log_decorator(title='个人信息', business_type=2)
async def change_system_user_profile_info(request: Request, user_info: UserInfoModel, query_db: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): async def change_system_user_profile_info(request: Request, user_info: UserInfoModel, query_db: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)):
try: try:
edit_user = EditUserModel(**user_info.model_dump(by_alias=True, exclude={'role_ids', 'post_ids'}), roleIds=user_info.role_ids.split(','), postIds=user_info.post_ids.split(',')) edit_user = EditUserModel(
edit_user.user_id = current_user.user.user_id **user_info.model_dump(
edit_user.update_by = current_user.user.user_name exclude_unset=True,
edit_user.update_time = datetime.now() by_alias=True,
print(edit_user.model_dump()) 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 = UserService.edit_user_services(query_db, edit_user) edit_user_result = UserService.edit_user_services(query_db, edit_user)
if edit_user_result.is_success: if edit_user_result.is_success:
logger.info(edit_user_result.message) logger.info(edit_user_result.message)
@@ -217,12 +226,12 @@ async def change_system_user_profile_info(request: Request, user_info: UserInfoM
@userController.put("/profile/updatePwd") @userController.put("/profile/updatePwd")
@log_decorator(title='个人信息', business_type=2) @log_decorator(title='个人信息', business_type=2)
async def reset_system_user_password(request: Request, old_password: str = Query(alias='oldPassword'), new_password: str = Query(alias='newPassword'), query_db: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): async def reset_system_user_password(request: Request, reset_password: ResetPasswordModel = Depends(ResetPasswordModel.as_query), query_db: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)):
try: try:
reset_user = ResetUserModel( reset_user = ResetUserModel(
userId=current_user.user.user_id, userId=current_user.user.user_id,
oldPassword=old_password, oldPassword=reset_password.old_password,
password=PwdUtil.get_password_hash(new_password), password=PwdUtil.get_password_hash(reset_password.new_password),
updateBy=current_user.user.user_name, updateBy=current_user.user.user_name,
updateTime=datetime.now() updateTime=datetime.now()
) )

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

@@ -51,7 +51,7 @@ class NoticeDao:
""" """
query = db.query(SysNotice) \ query = db.query(SysNotice) \
.filter(SysNotice.notice_title.like(f'%{query_object.notice_title}%') if query_object.notice_title else True, .filter(SysNotice.notice_title.like(f'%{query_object.notice_title}%') if query_object.notice_title else True,
SysNotice.update_by.like(f'%{query_object.update_by}%') if query_object.update_by 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.notice_type == query_object.notice_type if query_object.notice_type else True,
SysNotice.create_time.between( 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.begin_time, '%Y-%m-%d'), time(00, 00, 00)),

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

@@ -1,6 +1,8 @@
from pydantic import BaseModel, ConfigDict import re
from pydantic import BaseModel, ConfigDict, model_validator
from pydantic.alias_generators import to_camel from pydantic.alias_generators import to_camel
from typing import Optional from typing import Optional
from exceptions.exception import ModelValidatorException
class UserLogin(BaseModel): class UserLogin(BaseModel):
@@ -23,6 +25,14 @@ class UserRegister(BaseModel):
code: Optional[str] = None code: Optional[str] = None
uuid: Optional[str] = None uuid: Optional[str] = None
@model_validator(mode='after')
def check_password(self) -> 'UserRegister':
pattern = r'''^[^<>"'|\\]+$'''
if self.password is None or re.match(pattern, self.password):
return self
else:
raise ModelValidatorException(message="密码不能包含非法字符:< > \" ' \\ |")
class Token(BaseModel): class Token(BaseModel):
access_token: str access_token: str

View File

@@ -1,3 +1,4 @@
import re
from pydantic import BaseModel, ConfigDict, model_validator from pydantic import BaseModel, ConfigDict, model_validator
from pydantic.alias_generators import to_camel from pydantic.alias_generators import to_camel
from typing import Union, Optional, List from typing import Union, Optional, List
@@ -6,6 +7,7 @@ from module_admin.entity.vo.role_vo import RoleModel
from module_admin.entity.vo.dept_vo import DeptModel from module_admin.entity.vo.dept_vo import DeptModel
from module_admin.entity.vo.post_vo import PostModel from module_admin.entity.vo.post_vo import PostModel
from module_admin.annotation.pydantic_annotation import as_query, as_form from module_admin.annotation.pydantic_annotation import as_query, as_form
from exceptions.exception import ModelValidatorException
class TokenData(BaseModel): class TokenData(BaseModel):
@@ -42,6 +44,14 @@ class UserModel(BaseModel):
remark: Optional[str] = None remark: Optional[str] = None
admin: Optional[bool] = False admin: Optional[bool] = False
@model_validator(mode='after')
def check_password(self) -> 'UserModel':
pattern = r'''^[^<>"'|\\]+$'''
if self.password is None or re.match(pattern, self.password):
return self
else:
raise ModelValidatorException(message="密码不能包含非法字符:< > \" ' \\ |")
@model_validator(mode='after') @model_validator(mode='after')
def check_admin(self) -> 'UserModel': def check_admin(self) -> 'UserModel':
if self.user_id == 1: if self.user_id == 1:
@@ -144,6 +154,25 @@ class EditUserModel(AddUserModel):
role: Optional[List] = [] role: Optional[List] = []
@as_query
class ResetPasswordModel(BaseModel):
"""
重置密码模型
"""
model_config = ConfigDict(alias_generator=to_camel)
old_password: Optional[str] = None
new_password: Optional[str] = None
@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
else:
raise ModelValidatorException(message="密码不能包含非法字符:< > \" ' \\ |")
class ResetUserModel(UserModel): class ResetUserModel(UserModel):
""" """
重置用户密码模型 重置用户密码模型

View File

@@ -72,6 +72,7 @@ class JobService:
if query_job: if query_job:
SchedulerUtil.remove_scheduler_job(job_id=edit_job.get('job_id')) SchedulerUtil.remove_scheduler_job(job_id=edit_job.get('job_id'))
if edit_job.get('status') == '0': if edit_job.get('status') == '0':
job_info = cls.job_detail_services(query_db, edit_job.get('job_id'))
SchedulerUtil.add_scheduler_job(job_info=job_info) SchedulerUtil.add_scheduler_job(job_info=job_info)
query_db.commit() query_db.commit()
result = dict(is_success=True, message='更新成功') result = dict(is_success=True, message='更新成功')

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):
""" """
@@ -166,14 +182,18 @@ class LoginService:
if query_user.get('user_basic_info') is None: if query_user.get('user_basic_info') is None:
logger.warning("用户token不合法") logger.warning("用户token不合法")
raise AuthException(data="", message="用户token不合法") raise AuthException(data="", message="用户token不合法")
if AppConfig.app_same_time_login:
redis_token = await request.app.state.redis.get(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{session_id}") redis_token = await request.app.state.redis.get(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{session_id}")
else:
# 此方法可实现同一账号同一时间只能登录一次 # 此方法可实现同一账号同一时间只能登录一次
# redis_token = await request.app.state.redis.get(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{user.user_basic_info.user_id}") redis_token = await request.app.state.redis.get(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{query_user.get('user_basic_info').user_id}")
if token == redis_token: if token == redis_token:
if AppConfig.app_same_time_login:
await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{session_id}", redis_token, await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{session_id}", redis_token,
ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes)) ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes))
# await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{user.user_basic_info.user_id}", redis_token, else:
# ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes)) await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('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')] role_id_list = [item.role_id for item in query_user.get('user_role_info')]
if 1 in role_id_list: if 1 in role_id_list:
@@ -209,7 +229,7 @@ class LoginService:
:return: 当前用户路由信息对象 :return: 当前用户路由信息对象
""" """
query_user = UserDao.get_user_by_id(query_db, user_id=user_id) query_user = UserDao.get_user_by_id(query_db, user_id=user_id)
user_router_menu = [row for row in query_user.get('user_menu_info') if row.menu_type in ['M', 'C']] user_router_menu = sorted([row for row in query_user.get('user_menu_info') if row.menu_type in ['M', 'C']], key=lambda x: x.order_num)
user_router = cls.__generate_user_router_menu(0, user_router_menu) user_router = cls.__generate_user_router_menu(0, user_router_menu)
return user_router return user_router
@@ -229,14 +249,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
@@ -249,6 +271,7 @@ class LoginService:
elif permission.menu_type == 'C': elif permission.menu_type == 'C':
router_list_data['name'] = permission.path.capitalize() router_list_data['name'] = permission.path.capitalize()
router_list_data['path'] = permission.path router_list_data['path'] = permission.path
router_list_data['query'] = permission.query
router_list_data['hidden'] = False if permission.visible == '0' else True router_list_data['hidden'] = False if permission.visible == '0' else True
router_list_data['component'] = permission.component router_list_data['component'] = permission.component
router_list_data['meta'] = { router_list_data['meta'] = {

View File

@@ -131,7 +131,7 @@ class RoleService:
:param page_object: 角色数据权限对象 :param page_object: 角色数据权限对象
:return: 分配角色数据权限结果 :return: 分配角色数据权限结果
""" """
edit_role = page_object.model_dump(exclude_unset=True) edit_role = page_object.model_dump(exclude_unset=True, exclude={'admin'})
del edit_role['dept_ids'] del edit_role['dept_ids']
role_info = cls.role_detail_services(query_db, edit_role.get('role_id')) role_info = cls.role_detail_services(query_db, edit_role.get('role_id'))
if role_info: if role_info:

View File

@@ -206,7 +206,7 @@ class UserService:
:param page_object: 重置用户对象 :param page_object: 重置用户对象
:return: 重置用户校验结果 :return: 重置用户校验结果
""" """
reset_user = page_object.model_dump(exclude_unset=True) reset_user = page_object.model_dump(exclude_unset=True, exclude={'admin'})
if page_object.old_password: if page_object.old_password:
user = UserDao.get_user_detail_by_id(query_db, user_id=page_object.user_id).get('user_basic_info') user = 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): if not PwdUtil.verify_password(page_object.old_password, user.password):

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

@@ -4,5 +4,5 @@ VITE_APP_TITLE = vfadmin管理系统
# 开发环境配置 # 开发环境配置
VITE_APP_ENV = 'development' VITE_APP_ENV = 'development'
# 若依管理系统/开发环境 # vfadmin管理系统/开发环境
VITE_APP_BASE_API = '/dev-api' VITE_APP_BASE_API = '/dev-api'

View File

@@ -4,7 +4,7 @@ VITE_APP_TITLE = vfadmin管理系统
# 生产环境配置 # 生产环境配置
VITE_APP_ENV = 'production' VITE_APP_ENV = 'production'
# 若依管理系统/生产环境 # vfadmin管理系统/生产环境
VITE_APP_BASE_API = '/prod-api' VITE_APP_BASE_API = '/prod-api'
# 是否在打包时开启压缩,支持 gzip 和 brotli # 是否在打包时开启压缩,支持 gzip 和 brotli

View File

@@ -1,10 +1,10 @@
# 页面标题 # 页面标题
VITE_APP_TITLE = 若依管理系统 VITE_APP_TITLE = vfadmin管理系统
# 生产环境配置 # 生产环境配置
VITE_APP_ENV = 'staging' VITE_APP_ENV = 'staging'
# 若依管理系统/生产环境 # vfadmin管理系统/生产环境
VITE_APP_BASE_API = '/stage-api' VITE_APP_BASE_API = '/stage-api'
# 是否在打包时开启压缩,支持 gzip 和 brotli # 是否在打包时开启压缩,支持 gzip 和 brotli

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

@@ -7,7 +7,7 @@
<meta name="renderer" content="webkit"> <meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<title>若依管理系统</title> <title>vfadmin管理系统</title>
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]--> <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
<style> <style>
html, html,

View File

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

View File

@@ -105,7 +105,8 @@ const registerRules = {
], ],
password: [ password: [
{ required: true, trigger: "blur", message: "请输入您的密码" }, { required: true, trigger: "blur", message: "请输入您的密码" },
{ min: 5, max: 20, message: "用户密码长度必须介于 5 和 20 之间", trigger: "blur" } { min: 5, max: 20, message: "用户密码长度必须介于 5 和 20 之间", trigger: "blur" },
{ pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }
], ],
confirmPassword: [ confirmPassword: [
{ required: true, trigger: "blur", message: "请再次输入您的密码" }, { required: true, trigger: "blur", message: "请再次输入您的密码" },

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>
@@ -339,8 +339,8 @@ function reset() {
icon: undefined, icon: undefined,
menuType: "M", menuType: "M",
orderNum: undefined, orderNum: undefined,
isFrame: "1", isFrame: 1,
isCache: "0", isCache: 0,
visible: "0", visible: "0",
status: "0" status: "0"
}; };

View File

@@ -391,7 +391,7 @@ const data = reactive({
rules: { rules: {
userName: [{ required: true, message: "用户名称不能为空", trigger: "blur" }, { min: 2, max: 20, message: "用户名称长度必须介于 2 和 20 之间", trigger: "blur" }], userName: [{ required: true, message: "用户名称不能为空", trigger: "blur" }, { min: 2, max: 20, message: "用户名称长度必须介于 2 和 20 之间", trigger: "blur" }],
nickName: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }], nickName: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }],
password: [{ required: true, message: "用户密码不能为空", trigger: "blur" }, { min: 5, max: 20, message: "用户密码长度必须介于 5 和 20 之间", trigger: "blur" }], password: [{ required: true, message: "用户密码不能为空", trigger: "blur" }, { min: 5, max: 20, message: "用户密码长度必须介于 5 和 20 之间", trigger: "blur" }, { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }],
email: [{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }], email: [{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
phonenumber: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }] phonenumber: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }]
} }
@@ -494,6 +494,11 @@ function handleResetPwd(row) {
closeOnClickModal: false, closeOnClickModal: false,
inputPattern: /^.{5,20}$/, inputPattern: /^.{5,20}$/,
inputErrorMessage: "用户密码长度必须介于 5 和 20 之间", inputErrorMessage: "用户密码长度必须介于 5 和 20 之间",
inputValidator: (value) => {
if (/<|>|"|'|\||\\/.test(value)) {
return "不能包含非法字符:< > \" ' \\\ |"
}
},
}).then(({ value }) => { }).then(({ value }) => {
resetUserPwd(row.userId, value).then(response => { resetUserPwd(row.userId, value).then(response => {
proxy.$modal.msgSuccess("修改成功,新密码是:" + value); proxy.$modal.msgSuccess("修改成功,新密码是:" + value);

View File

@@ -36,7 +36,7 @@ const equalToPassword = (rule, value, callback) => {
}; };
const rules = ref({ const rules = ref({
oldPassword: [{ required: true, message: "旧密码不能为空", trigger: "blur" }], oldPassword: [{ required: true, message: "旧密码不能为空", trigger: "blur" }],
newPassword: [{ required: true, message: "新密码不能为空", trigger: "blur" }, { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" }], newPassword: [{ required: true, message: "新密码不能为空", trigger: "blur" }, { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" }, { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }],
confirmPassword: [{ required: true, message: "确认密码不能为空", trigger: "blur" }, { required: true, validator: equalToPassword, trigger: "blur" }] confirmPassword: [{ required: true, message: "确认密码不能为空", trigger: "blur" }, { required: true, validator: equalToPassword, trigger: "blur" }]
}); });