!29 RuoYi-Vue3-FastAPI v1.6.1

Merge pull request !29 from insistence/develop
This commit is contained in:
insistence
2025-03-03 08:50:47 +00:00
committed by Gitee
11 changed files with 61 additions and 27 deletions

View File

@@ -1,12 +1,12 @@
<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.6.0</h1> <h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi-Vue3-FastAPI v1.6.1</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.6.0-brightgreen.svg"></a> <a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI"><img src="https://img.shields.io/badge/RuoYiVue3FastAPI-v1.6.1-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.9-blue"> <img src="https://img.shields.io/badge/python-≥3.9-blue">
<img src="https://img.shields.io/badge/MySQL-≥5.7-blue"> <img src="https://img.shields.io/badge/MySQL-≥5.7-blue">

View File

@@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0'
# 应用端口 # 应用端口
APP_PORT = 9099 APP_PORT = 9099
# 应用版本 # 应用版本
APP_VERSION= '1.6.0' APP_VERSION= '1.6.1'
# 应用是否开启热重载 # 应用是否开启热重载
APP_RELOAD = true APP_RELOAD = true
# 应用是否开启IP归属区域查询 # 应用是否开启IP归属区域查询

View File

@@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0'
# 应用端口 # 应用端口
APP_PORT = 9099 APP_PORT = 9099
# 应用版本 # 应用版本
APP_VERSION= '1.6.0' APP_VERSION= '1.6.1'
# 应用是否开启热重载 # 应用是否开启热重载
APP_RELOAD = false APP_RELOAD = false
# 应用是否开启IP归属区域查询 # 应用是否开启IP归属区域查询

View File

@@ -3,6 +3,7 @@ import os
import sys import sys
from dotenv import load_dotenv from dotenv import load_dotenv
from functools import lru_cache from functools import lru_cache
from pydantic import computed_field
from pydantic_settings import BaseSettings from pydantic_settings import BaseSettings
from typing import Literal from typing import Literal
@@ -51,6 +52,13 @@ class DataBaseSettings(BaseSettings):
db_pool_recycle: int = 3600 db_pool_recycle: int = 3600
db_pool_timeout: int = 30 db_pool_timeout: int = 30
@computed_field
@property
def sqlglot_parse_dialect(self) -> str:
if self.db_type == 'postgresql':
return 'postgres'
return self.db_type
class RedisSettings(BaseSettings): class RedisSettings(BaseSettings):
""" """

View File

@@ -2,10 +2,13 @@ import inspect
from fastapi import Form, Query from fastapi import Form, Query
from pydantic import BaseModel from pydantic import BaseModel
from pydantic.fields import FieldInfo from pydantic.fields import FieldInfo
from typing import Type from typing import Type, TypeVar
def as_query(cls: Type[BaseModel]): BaseModelVar = TypeVar('BaseModelVar', bound=BaseModel)
def as_query(cls: Type[BaseModelVar]) -> Type[BaseModelVar]:
""" """
pydantic模型查询参数装饰器将pydantic模型用于接收查询参数 pydantic模型查询参数装饰器将pydantic模型用于接收查询参数
""" """
@@ -43,7 +46,7 @@ def as_query(cls: Type[BaseModel]):
return cls return cls
def as_form(cls: Type[BaseModel]): def as_form(cls: Type[BaseModelVar]) -> Type[BaseModelVar]:
""" """
pydantic模型表单参数装饰器将pydantic模型用于接收表单参数 pydantic模型表单参数装饰器将pydantic模型用于接收表单参数
""" """

View File

@@ -2,6 +2,7 @@ from datetime import datetime, time
from sqlalchemy import delete, func, select, text, update from sqlalchemy import delete, func, select, text, update
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from sqlglot.expressions import Expression
from typing import List from typing import List
from config.env import DataBaseConfig from config.env import DataBaseConfig
from module_generator.entity.do.gen_do import GenTable, GenTableColumn from module_generator.entity.do.gen_do import GenTable, GenTableColumn
@@ -75,15 +76,17 @@ class GenTableDao:
return gen_table_all return gen_table_all
@classmethod @classmethod
async def create_table_by_sql_dao(cls, db: AsyncSession, sql: str): async def create_table_by_sql_dao(cls, db: AsyncSession, sql_statements: List[Expression]):
""" """
根据sql语句创建表结构 根据sql语句创建表结构
:param db: orm对象 :param db: orm对象
:param sql: sql语句 :param sql_statements: sql语句的ast列表
:return: :return:
""" """
await db.execute(text(sql)) for sql_statement in sql_statements:
sql = sql_statement.sql(dialect=DataBaseConfig.sqlglot_parse_dialect)
await db.execute(text(sql))
@classmethod @classmethod
async def get_gen_table_list(cls, db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False): async def get_gen_table_list(cls, db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False):

View File

@@ -1,13 +1,14 @@
import io import io
import json import json
import os import os
import re
import zipfile import zipfile
from datetime import datetime from datetime import datetime
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlglot import parse as sqlglot_parse
from sqlglot.expressions import Add, Alter, Create, Delete, Drop, Expression, Insert, Table, TruncateTable, Update
from typing import List from typing import List
from config.constant import GenConstant from config.constant import GenConstant
from config.env import GenConfig from config.env import DataBaseConfig, GenConfig
from exceptions.exception import ServiceException from exceptions.exception import ServiceException
from module_admin.entity.vo.common_vo import CrudResponseModel from module_admin.entity.vo.common_vo import CrudResponseModel
from module_admin.entity.vo.user_vo import CurrentUserModel from module_admin.entity.vo.user_vo import CurrentUserModel
@@ -197,10 +198,11 @@ class GenTableService:
:param current_user: 当前用户信息对象 :param current_user: 当前用户信息对象
:return: 创建表结构结果 :return: 创建表结构结果
""" """
if cls.__is_valid_create_table(sql): sql_statements = sqlglot_parse(sql, dialect=DataBaseConfig.sqlglot_parse_dialect)
if cls.__is_valid_create_table(sql_statements):
try: try:
table_names = re.findall(r'create\s+table\s+(\w+)', sql, re.IGNORECASE) table_names = cls.__get_table_names(sql_statements)
await GenTableDao.create_table_by_sql_dao(query_db, sql) await GenTableDao.create_table_by_sql_dao(query_db, sql_statements)
gen_table_list = await cls.get_gen_db_table_list_by_name_services(query_db, table_names) gen_table_list = await cls.get_gen_db_table_list_by_name_services(query_db, table_names)
await cls.import_gen_table_services(query_db, gen_table_list, current_user) await cls.import_gen_table_services(query_db, gen_table_list, current_user)
@@ -211,22 +213,39 @@ class GenTableService:
raise ServiceException(message='建表语句不合法') raise ServiceException(message='建表语句不合法')
@classmethod @classmethod
def __is_valid_create_table(cls, sql: str): def __is_valid_create_table(cls, sql_statements: List[Expression]):
""" """
校验sql语句是否为合法的建表语句 校验sql语句是否为合法的建表语句
:param sql: sql语句 :param sql_statements: sql语句的ast列表
:return: 校验结果 :return: 校验结果
""" """
create_table_pattern = r'^\s*CREATE\s+TABLE\s+' validate_create = [isinstance(sql_statement, Create) for sql_statement in sql_statements]
if not re.search(create_table_pattern, sql, re.IGNORECASE): validate_forbidden_keywords = [
isinstance(
sql_statement,
(Add, Alter, Delete, Drop, Insert, TruncateTable, Update),
)
for sql_statement in sql_statements
]
if not any(validate_create) or any(validate_forbidden_keywords):
return False return False
forbidden_keywords = ['INSERT', 'UPDATE', 'DELETE', 'DROP', 'ALTER', 'TRUNCATE']
for keyword in forbidden_keywords:
if re.search(rf'\b{keyword}\b', sql, re.IGNORECASE):
return False
return True return True
@classmethod
def __get_table_names(cls, sql_statements: List[Expression]):
"""
获取sql语句中所有的建表表名
:param sql_statements: sql语句的ast列表
:return: 建表表名列表
"""
table_names = []
for sql_statement in sql_statements:
if isinstance(sql_statement, Create):
table_names.append(sql_statement.find(Table).name)
return table_names
@classmethod @classmethod
async def preview_code_services(cls, query_db: AsyncSession, table_id: int): async def preview_code_services(cls, query_db: AsyncSession, table_id: int):
""" """

View File

@@ -70,7 +70,7 @@ class {{ BusinessName }}Dao:
await db.execute( await db.execute(
select({{ ClassName }}).where( select({{ ClassName }}).where(
{% for column in columns %} {% for column in columns %}
{% if column.required %} {% if column.unique %}
{{ ClassName }}.{{ column.python_field | camel_to_snake }} == {{ businessName }}.{{ column.python_field | camel_to_snake }} if {{ businessName }}.{{ column.python_field | camel_to_snake }} else True, {{ ClassName }}.{{ column.python_field | camel_to_snake }} == {{ businessName }}.{{ column.python_field | camel_to_snake }} if {{ businessName }}.{{ column.python_field | camel_to_snake }} else True,
{% endif %} {% endif %}
{% endfor %} {% endfor %}

View File

@@ -8,8 +8,8 @@
{% set vo_field_required.has_required = True %} {% set vo_field_required.has_required = True %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if table.sub %}
{% set sub_vo_field_required = namespace(has_required=False) %} {% set sub_vo_field_required = namespace(has_required=False) %}
{% if table.sub %}
{% for sub_column in subTable.columns %} {% for sub_column in subTable.columns %}
{% if sub_column.required %} {% if sub_column.required %}
{% set sub_vo_field_required.has_required = True %} {% set sub_vo_field_required.has_required = True %}
@@ -21,7 +21,7 @@
{% endfor %} {% endfor %}
from pydantic import BaseModel, ConfigDict, Field from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel from pydantic.alias_generators import to_camel
{% if vo_field_required.has_required %} {% if vo_field_required.has_required or sub_vo_field_required.has_required %}
from pydantic_validation_decorator import NotBlank from pydantic_validation_decorator import NotBlank
{% endif %} {% endif %}
{% if table.sub %} {% if table.sub %}

View File

@@ -14,4 +14,5 @@ PyMySQL==1.1.1
redis==5.2.1 redis==5.2.1
requests==2.32.3 requests==2.32.3
SQLAlchemy[asyncio]==2.0.38 SQLAlchemy[asyncio]==2.0.38
sqlglot[rs]==26.6.0
user-agents==2.2.0 user-agents==2.2.0

View File

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