diff --git a/README.md b/README.md index e6442553c911423f2244a5c5f9e03de4272c4731..2b8d7f5ddaa074abc2e0f5eaad867acf87e0941e 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@

logo

-

RuoYi-Vue-FastAPI v1.1.2

+

RuoYi-Vue-FastAPI v1.1.3

基于RuoYi-Vue+FastAPI前后端分离的快速开发框架

- + @@ -18,6 +18,7 @@ + ## 平台简介 RuoYi-Vue-FastAPI是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。 diff --git a/ruoyi-fastapi-backend/.env.dev b/ruoyi-fastapi-backend/.env.dev index 6249b39f8240c8cd3cbf38c2e801722c5b6b4da2..e3916a844f6681d669f614cf8f8dfa3f8c696c2c 100644 --- a/ruoyi-fastapi-backend/.env.dev +++ b/ruoyi-fastapi-backend/.env.dev @@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0' # 应用端口 APP_PORT = 9099 # 应用版本 -APP_VERSION= '1.1.2' +APP_VERSION= '1.1.3' # 应用是否开启热重载 APP_RELOAD = true # 应用是否开启IP归属区域查询 diff --git a/ruoyi-fastapi-backend/.env.prod b/ruoyi-fastapi-backend/.env.prod index c9e201f14b2acb52a091fc197284f50743bba802..b9132b58be6b4a0fe36fe154c137cae1f2232766 100644 --- a/ruoyi-fastapi-backend/.env.prod +++ b/ruoyi-fastapi-backend/.env.prod @@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0' # 应用端口 APP_PORT = 9099 # 应用版本 -APP_VERSION= '1.1.2' +APP_VERSION= '1.1.3' # 应用是否开启热重载 APP_RELOAD = false # 应用是否开启IP归属区域查询 diff --git a/ruoyi-fastapi-backend/exceptions/exception.py b/ruoyi-fastapi-backend/exceptions/exception.py index ef2fda06fdc21e8ea79bf5969483e4a97e7018c5..28c39a673a1ba1d6010033a8924f8c1a447a6323 100644 --- a/ruoyi-fastapi-backend/exceptions/exception.py +++ b/ruoyi-fastapi-backend/exceptions/exception.py @@ -26,3 +26,13 @@ class PermissionException(Exception): def __init__(self, data: str = None, message: str = None): self.data = data self.message = message + + +class ModelValidatorException(Exception): + """ + 自定义模型校验异常ModelValidatorException + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message diff --git a/ruoyi-fastapi-backend/exceptions/handle.py b/ruoyi-fastapi-backend/exceptions/handle.py index 61b7f9009f07b85e14f95399ee2671527792790a..040a11fb71d3a151d85061ec0276624b555d4ab0 100644 --- a/ruoyi-fastapi-backend/exceptions/handle.py +++ b/ruoyi-fastapi-backend/exceptions/handle.py @@ -1,6 +1,6 @@ from fastapi import FastAPI, Request 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 @@ -18,6 +18,11 @@ def handle_exception(app: FastAPI): async def permission_exception_handler(request: Request, exc: PermissionException): 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请求异常 @app.exception_handler(HTTPException) async def http_exception_handler(request: Request, exc: HTTPException): diff --git a/ruoyi-fastapi-backend/module_admin/controller/user_controller.py b/ruoyi-fastapi-backend/module_admin/controller/user_controller.py index 46ea0b82f9c708c8b535c67427978a4b5d25b42d..9385a6d960c9c9d061cadd93f37be220c2d61fdb 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/user_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/user_controller.py @@ -198,11 +198,20 @@ async def change_system_user_profile_avatar(request: Request, avatarfile: bytes @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)): 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.user_id = current_user.user.user_id - edit_user.update_by = current_user.user.user_name - edit_user.update_time = datetime.now() - print(edit_user.model_dump()) + edit_user = EditUserModel( + **user_info.model_dump( + exclude_unset=True, + by_alias=True, + exclude={'role_ids', 'post_ids'} + ), + userId=current_user.user.user_id, + userName=current_user.user.user_name, + updateBy=current_user.user.user_name, + updateTime=datetime.now(), + roleIds=current_user.user.role_ids.split(',') if current_user.user.role_ids else [], + postIds=current_user.user.post_ids.split(',') if current_user.user.post_ids else [], + role=current_user.user.role + ) edit_user_result = UserService.edit_user_services(query_db, edit_user) if edit_user_result.is_success: 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") @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: reset_user = ResetUserModel( userId=current_user.user.user_id, - oldPassword=old_password, - password=PwdUtil.get_password_hash(new_password), + oldPassword=reset_password.old_password, + password=PwdUtil.get_password_hash(reset_password.new_password), updateBy=current_user.user.user_name, updateTime=datetime.now() ) diff --git a/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py b/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py index 2d3646f420496263489c7c29f1f16610e5c4073b..79f32a3016600551cd00829a01d81958fd7eb014 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py @@ -51,7 +51,7 @@ class NoticeDao: """ query = db.query(SysNotice) \ .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.create_time.between( datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py index 09a595f2a7cbbaf257045debc372790010e2d0a0..13ad73ad5e5bae602116c9b262a204fb95b9e219 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py @@ -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 typing import Optional +from exceptions.exception import ModelValidatorException class UserLogin(BaseModel): @@ -23,6 +25,14 @@ class UserRegister(BaseModel): code: 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): access_token: str diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py index fbc39831eb2976d5e6796f0191850f539b1b7352..fa761ef5048da3482dfd9977ce2a61ea020398fa 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py @@ -1,3 +1,4 @@ +import re from pydantic import BaseModel, ConfigDict, model_validator from pydantic.alias_generators import to_camel 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.post_vo import PostModel from module_admin.annotation.pydantic_annotation import as_query, as_form +from exceptions.exception import ModelValidatorException class TokenData(BaseModel): @@ -42,6 +44,14 @@ class UserModel(BaseModel): remark: Optional[str] = None 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') def check_admin(self) -> 'UserModel': if self.user_id == 1: @@ -144,6 +154,25 @@ class EditUserModel(AddUserModel): 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): """ 重置用户密码模型 diff --git a/ruoyi-fastapi-frontend/package.json b/ruoyi-fastapi-frontend/package.json index 8b59cc2d8d853d023be9447ed12ff995f3c5cb16..437e611e0ab1965ac443e6dbea2fae4503cf501a 100644 --- a/ruoyi-fastapi-frontend/package.json +++ b/ruoyi-fastapi-frontend/package.json @@ -1,6 +1,6 @@ { "name": "vfadmin", - "version": "1.1.2", + "version": "1.1.3", "description": "vfadmin管理系统", "author": "insistence", "license": "MIT", @@ -71,7 +71,7 @@ "babel-plugin-dynamic-import-node": "2.3.3", "babel-plugin-import": "^1.13.8", "chalk": "4.1.0", - "compression-webpack-plugin": "5.0.2", + "compression-webpack-plugin": "6.1.2", "connect": "3.6.6", "eslint": "7.15.0", "eslint-plugin-vue": "7.2.0", diff --git a/ruoyi-fastapi-frontend/src/views/register.vue b/ruoyi-fastapi-frontend/src/views/register.vue index f660f39b80749f88ed39f6e2fc78de9594b12179..e0f8a3b8876814f930e6a7998f216cfee39d98fb 100644 --- a/ruoyi-fastapi-frontend/src/views/register.vue +++ b/ruoyi-fastapi-frontend/src/views/register.vue @@ -95,7 +95,8 @@ export default { ], password: [ { 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: [ { required: true, trigger: "blur", message: "请再次输入您的密码" }, diff --git a/ruoyi-fastapi-frontend/src/views/system/user/index.vue b/ruoyi-fastapi-frontend/src/views/system/user/index.vue index 582c1c53806e1d78d2d21903ffe21544fe1624b3..1666cf09c5248d0aa7fc71a94093e22d62ba070d 100644 --- a/ruoyi-fastapi-frontend/src/views/system/user/index.vue +++ b/ruoyi-fastapi-frontend/src/views/system/user/index.vue @@ -433,7 +433,8 @@ export default { ], password: [ { required: true, message: "用户密码不能为空", trigger: "blur" }, - { min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' } + { min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' }, + { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" } ], email: [ { @@ -591,7 +592,12 @@ export default { cancelButtonText: "取消", closeOnClickModal: false, inputPattern: /^.{5,20}$/, - inputErrorMessage: "用户密码长度必须介于 5 和 20 之间" + inputErrorMessage: "用户密码长度必须介于 5 和 20 之间", + inputValidator: (value) => { + if (/<|>|"|'|\||\\/.test(value)) { + return "不能包含非法字符:< > \" ' \\\ |" + } + }, }).then(({ value }) => { resetUserPwd(row.userId, value).then(response => { this.$modal.msgSuccess("修改成功,新密码是:" + value); diff --git a/ruoyi-fastapi-frontend/src/views/system/user/profile/resetPwd.vue b/ruoyi-fastapi-frontend/src/views/system/user/profile/resetPwd.vue index 64e8f8c4f4464a571a522b924422956a1718193e..f329e6e870dd8bc535bf4299befb4bf1accd25d0 100644 --- a/ruoyi-fastapi-frontend/src/views/system/user/profile/resetPwd.vue +++ b/ruoyi-fastapi-frontend/src/views/system/user/profile/resetPwd.vue @@ -41,7 +41,8 @@ export default { ], newPassword: [ { required: true, message: "新密码不能为空", trigger: "blur" }, - { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" } + { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" }, + { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" } ], confirmPassword: [ { required: true, message: "确认密码不能为空", trigger: "blur" }, diff --git a/ruoyi-fastapi-frontend/vue.config.js b/ruoyi-fastapi-frontend/vue.config.js index bdd2b2458b8e1048b3df6b77305ad3a45f16d7cd..71066a1507e7e1afc6806a3666929671b6edec12 100644 --- a/ruoyi-fastapi-frontend/vue.config.js +++ b/ruoyi-fastapi-frontend/vue.config.js @@ -64,11 +64,13 @@ module.exports = { plugins: [ // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件 new CompressionPlugin({ - cache: false, // 不启用文件缓存 - test: /\.(js|css|html)?$/i, // 压缩文件格式 - filename: '[path].gz[query]', // 压缩后的文件名 - algorithm: 'gzip', // 使用gzip压缩 - minRatio: 0.8 // 压缩率小于1才会压缩 + cache: false, // 不启用文件缓存 + test: /\.(js|css|html|jpe?g|png|gif|svg)?$/i, // 压缩文件格式 + filename: '[path][base].gz[query]', // 压缩后的文件名 + algorithm: 'gzip', // 使用gzip压缩 + // threshold: 10240, // 只有大于 10kb 的文件会被压缩 + minRatio: 0.8, // 压缩比例,小于 80% 的文件不会被压缩 + deleteOriginalAssets: false // 压缩后删除原文件 }) ], },