Skip to content

Commit b29a75d

Browse files
authored
perf: 优化项目启动速度 (#26)
1 parent e5fe9ca commit b29a75d

File tree

4 files changed

+115
-106
lines changed

4 files changed

+115
-106
lines changed

ruoyi-fastapi-backend/common/router.py

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import glob
12
import importlib
23
import os
34
import sys
@@ -306,17 +307,8 @@ def _find_controller_files(self) -> list[str]:
306307
307308
:return: py文件路径列表
308309
"""
309-
controller_files = []
310-
# 遍历所有目录,查找controller目录
311-
for root, _dirs, files in os.walk(self.project_root):
312-
# 检查当前目录是否为controller目录
313-
if os.path.basename(root) == 'controller':
314-
# 遍历controller目录下的所有py文件
315-
for file in files:
316-
if file.endswith('.py') and not file.startswith('__'):
317-
file_path = os.path.join(root, file)
318-
controller_files.append(file_path)
319-
return controller_files
310+
pattern = os.path.join(self.project_root, '*', 'controller', '[!_]*.py')
311+
return sorted(glob.glob(pattern))
320312

321313
def _import_module_and_get_routers(self, controller_files: list[str]) -> list[tuple[str, APIRouter]]:
322314
"""
@@ -333,9 +325,8 @@ def _import_module_and_get_routers(self, controller_files: list[str]) -> list[tu
333325

334326
# 动态导入模块
335327
module = importlib.import_module(module_name)
336-
# 遍历模块属性,寻找APIRouter和APIRouterPro实例
337-
for attr_name in dir(module):
338-
attr = getattr(module, attr_name)
328+
# 直接遍历模块__dict__,只检查模块自身定义的属性
329+
for attr_name, attr in module.__dict__.items():
339330
# 对于APIRouterPro实例,只有当auto_register=True时才添加
340331
if isinstance(attr, APIRouterPro):
341332
if attr.auto_register:

ruoyi-fastapi-backend/config/get_scheduler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ def __find_recent_workday(cls, day: int) -> int:
103103
'password': RedisConfig.redis_password,
104104
'db': RedisConfig.redis_database,
105105
}
106-
executors = {'default': AsyncIOExecutor(), 'processpool': ProcessPoolExecutor(5)}
107106
job_defaults = {'coalesce': False, 'max_instance': 1}
108107
scheduler = AsyncIOScheduler()
109108

@@ -186,6 +185,7 @@ def _configure_scheduler(cls) -> None:
186185
'sqlalchemy': SQLAlchemyJobStore(url=SYNC_SQLALCHEMY_DATABASE_URL, engine=cls._get_jobstore_engine()),
187186
'redis': RedisJobStore(**redis_config),
188187
}
188+
executors = {'default': AsyncIOExecutor(), 'processpool': ProcessPoolExecutor(5)}
189189
scheduler.configure(jobstores=job_stores, executors=executors, job_defaults=job_defaults)
190190
cls._scheduler_configured = True
191191

Lines changed: 97 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,115 @@
1-
from agno.db.base import AsyncBaseDb
2-
from agno.db.mysql import AsyncMySQLDb
3-
from agno.db.postgres import AsyncPostgresDb
4-
from agno.models.aimlapi import AIMLAPI
5-
from agno.models.anthropic import Claude
6-
from agno.models.base import Model
7-
from agno.models.cerebras import Cerebras, CerebrasOpenAI
8-
from agno.models.cohere import Cohere
9-
from agno.models.cometapi import CometAPI
10-
from agno.models.dashscope import DashScope
11-
from agno.models.deepinfra import DeepInfra
12-
from agno.models.deepseek import DeepSeek
13-
from agno.models.fireworks import Fireworks
14-
from agno.models.google import Gemini
15-
from agno.models.groq import Groq
16-
from agno.models.huggingface import HuggingFace
17-
from agno.models.langdb import LangDB
18-
from agno.models.litellm import LiteLLM, LiteLLMOpenAI
19-
from agno.models.llama_cpp import LlamaCpp
20-
from agno.models.lmstudio import LMStudio
21-
from agno.models.meta import Llama
22-
from agno.models.mistral import MistralChat
23-
from agno.models.n1n import N1N
24-
from agno.models.nebius import Nebius
25-
from agno.models.nexus import Nexus
26-
from agno.models.nvidia import Nvidia
27-
from agno.models.ollama import Ollama
28-
from agno.models.openai import OpenAIChat
29-
from agno.models.openai.responses import OpenAIResponses
30-
from agno.models.openrouter import OpenRouter
31-
from agno.models.perplexity import Perplexity
32-
from agno.models.portkey import Portkey
33-
from agno.models.requesty import Requesty
34-
from agno.models.sambanova import Sambanova
35-
from agno.models.siliconflow import Siliconflow
36-
from agno.models.together import Together
37-
from agno.models.vercel import V0
38-
from agno.models.vllm import VLLM
39-
from agno.models.xai import xAI
1+
from importlib import import_module
2+
from typing import TYPE_CHECKING
403

414
from config.database import async_engine
425
from config.env import DataBaseConfig
436

44-
provider_model_map: dict[str, type[Model]] = {
45-
'AIMLAPI': AIMLAPI,
46-
'Anthropic': Claude,
47-
'Cerebras': Cerebras,
48-
'CerebrasOpenAI': CerebrasOpenAI,
49-
'Cohere': Cohere,
50-
'CometAPI': CometAPI,
51-
'DashScope': DashScope,
52-
'DeepInfra': DeepInfra,
53-
'DeepSeek': DeepSeek,
54-
'Fireworks': Fireworks,
55-
'Google': Gemini,
56-
'Groq': Groq,
57-
'HuggingFace': HuggingFace,
58-
'LangDB': LangDB,
59-
'LiteLLM': LiteLLM,
60-
'LiteLLMOpenAI': LiteLLMOpenAI,
61-
'LlamaCpp': LlamaCpp,
62-
'LMStudio': LMStudio,
63-
'Meta': Llama,
64-
'Mistral': MistralChat,
65-
'N1N': N1N,
66-
'Nebius': Nebius,
67-
'Nexus': Nexus,
68-
'Nvidia': Nvidia,
69-
'Ollama': Ollama,
70-
'OpenAI': OpenAIChat,
71-
'OpenAIResponses': OpenAIResponses,
72-
'OpenRouter': OpenRouter,
73-
'Perplexity': Perplexity,
74-
'Portkey': Portkey,
75-
'Requesty': Requesty,
76-
'Sambanova': Sambanova,
77-
'SiliconFlow': Siliconflow,
78-
'Together': Together,
79-
'Vercel': V0,
80-
'VLLM': VLLM,
81-
'xAI': xAI,
82-
}
7+
if TYPE_CHECKING:
8+
from agno.db.base import AsyncBaseDb
9+
from agno.models.base import Model
8310

11+
# 提供商名称 -> (模块路径, 类名) 的映射,延迟导入避免启动时加载所有AI SDK
12+
_PROVIDER_REGISTRY: dict[str, tuple[str, str]] = {
13+
'AIMLAPI': ('agno.models.aimlapi', 'AIMLAPI'),
14+
'Anthropic': ('agno.models.anthropic', 'Claude'),
15+
'Cerebras': ('agno.models.cerebras', 'Cerebras'),
16+
'CerebrasOpenAI': ('agno.models.cerebras', 'CerebrasOpenAI'),
17+
'Cohere': ('agno.models.cohere', 'Cohere'),
18+
'CometAPI': ('agno.models.cometapi', 'CometAPI'),
19+
'DashScope': ('agno.models.dashscope', 'DashScope'),
20+
'DeepInfra': ('agno.models.deepinfra', 'DeepInfra'),
21+
'DeepSeek': ('agno.models.deepseek', 'DeepSeek'),
22+
'Fireworks': ('agno.models.fireworks', 'Fireworks'),
23+
'Google': ('agno.models.google', 'Gemini'),
24+
'Groq': ('agno.models.groq', 'Groq'),
25+
'HuggingFace': ('agno.models.huggingface', 'HuggingFace'),
26+
'LangDB': ('agno.models.langdb', 'LangDB'),
27+
'LiteLLM': ('agno.models.litellm', 'LiteLLM'),
28+
'LiteLLMOpenAI': ('agno.models.litellm', 'LiteLLMOpenAI'),
29+
'LlamaCpp': ('agno.models.llama_cpp', 'LlamaCpp'),
30+
'LMStudio': ('agno.models.lmstudio', 'LMStudio'),
31+
'Meta': ('agno.models.meta', 'Llama'),
32+
'Mistral': ('agno.models.mistral', 'MistralChat'),
33+
'N1N': ('agno.models.n1n', 'N1N'),
34+
'Nebius': ('agno.models.nebius', 'Nebius'),
35+
'Nexus': ('agno.models.nexus', 'Nexus'),
36+
'Nvidia': ('agno.models.nvidia', 'Nvidia'),
37+
'Ollama': ('agno.models.ollama', 'Ollama'),
38+
'OpenAI': ('agno.models.openai', 'OpenAIChat'),
39+
'OpenAIResponses': ('agno.models.openai.responses', 'OpenAIResponses'),
40+
'OpenRouter': ('agno.models.openrouter', 'OpenRouter'),
41+
'Perplexity': ('agno.models.perplexity', 'Perplexity'),
42+
'Portkey': ('agno.models.portkey', 'Portkey'),
43+
'Requesty': ('agno.models.requesty', 'Requesty'),
44+
'Sambanova': ('agno.models.sambanova', 'Sambanova'),
45+
'SiliconFlow': ('agno.models.siliconflow', 'Siliconflow'),
46+
'Together': ('agno.models.together', 'Together'),
47+
'Vercel': ('agno.models.vercel', 'V0'),
48+
'VLLM': ('agno.models.vllm', 'VLLM'),
49+
'xAI': ('agno.models.xai', 'xAI'),
50+
}
8451

85-
storage_engine_map: dict[str, type[AsyncBaseDb]] = {
86-
'mysql': AsyncMySQLDb,
87-
'postgresql': AsyncPostgresDb,
52+
# 存储引擎名称 -> (模块路径, 类名) 的映射
53+
_STORAGE_ENGINE_REGISTRY: dict[str, tuple[str, str]] = {
54+
'mysql': ('agno.db.mysql', 'AsyncMySQLDb'),
55+
'postgresql': ('agno.db.postgres', 'AsyncPostgresDb'),
8856
}
8957

58+
# 已加载的提供商类缓存,避免重复import_module
59+
_provider_class_cache: dict[str, 'type[Model]'] = {}
60+
_storage_class_cache: dict[str, 'type[AsyncBaseDb]'] = {}
61+
62+
63+
def _resolve_provider_class(provider: str) -> 'type[Model] | None':
64+
"""
65+
按需加载并缓存提供商模型类
66+
67+
:param provider: 提供商名称
68+
:return: 模型类,未找到返回None
69+
"""
70+
if provider in _provider_class_cache:
71+
return _provider_class_cache[provider]
72+
entry = _PROVIDER_REGISTRY.get(provider)
73+
if entry is None:
74+
return None
75+
module_path, class_name = entry
76+
cls = getattr(import_module(module_path), class_name)
77+
_provider_class_cache[provider] = cls
78+
return cls
79+
80+
81+
def _resolve_storage_class(db_type: str) -> 'type[AsyncBaseDb]':
82+
"""
83+
按需加载并缓存存储引擎类
84+
85+
:param db_type: 数据库类型
86+
:return: 存储引擎类
87+
"""
88+
if db_type in _storage_class_cache:
89+
return _storage_class_cache[db_type]
90+
entry = _STORAGE_ENGINE_REGISTRY.get(db_type)
91+
if entry is None:
92+
# 默认使用MySQL
93+
entry = _STORAGE_ENGINE_REGISTRY['mysql']
94+
module_path, class_name = entry
95+
cls = getattr(import_module(module_path), class_name)
96+
_storage_class_cache[db_type] = cls
97+
return cls
98+
9099

91100
class AiUtil:
92101
"""
93102
AI工具类
94103
"""
95104

96105
@classmethod
97-
def get_storage_engine(cls) -> AsyncBaseDb:
106+
def get_storage_engine(cls) -> 'AsyncBaseDb':
98107
"""
99108
获取存储引擎实例
100109
101110
:return: 存储引擎实例
102111
"""
103-
storage_engine_class = storage_engine_map.get(DataBaseConfig.db_type, AsyncMySQLDb)
112+
storage_engine_class = _resolve_storage_class(DataBaseConfig.db_type)
104113

105114
return storage_engine_class(
106115
db_engine=async_engine,
@@ -128,7 +137,7 @@ def get_model_from_factory(
128137
temperature: float | None = None,
129138
max_tokens: int | None = None,
130139
**kwargs,
131-
) -> Model:
140+
) -> 'Model':
132141
"""
133142
从工厂获取模型实例
134143
@@ -155,6 +164,9 @@ def get_model_from_factory(
155164
params['host'] = base_url
156165
if provider == 'DashScope' and not base_url:
157166
params['base_url'] = 'https://dashscope.aliyuncs.com/compatible-mode/v1'
158-
model_class = provider_model_map.get(provider, OpenAIChat)
167+
model_class = _resolve_provider_class(provider)
168+
if model_class is None:
169+
# 未知提供商,回退到OpenAI
170+
model_class = _resolve_provider_class('OpenAI')
159171

160172
return model_class(**params)

ruoyi-fastapi-backend/utils/server_util.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,9 @@ class IPUtil:
429429
IP工具类
430430
"""
431431

432+
_PREFERRED_DNS_HOSTS: tuple[str, str] = ('223.5.5.5', '8.8.8.8')
433+
_DNS_CONNECT_TIMEOUT = 1
434+
432435
@classmethod
433436
def get_local_ip(cls) -> str:
434437
"""
@@ -472,12 +475,15 @@ def get_network_ips(cls) -> list[str]:
472475

473476
# 优先显示首选出站IP
474477
preferred_ip = None
475-
try:
476-
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
477-
s.connect(('8.8.8.8', 80))
478-
preferred_ip = s.getsockname()[0]
479-
except Exception:
480-
pass
478+
for dns_host in cls._PREFERRED_DNS_HOSTS:
479+
try:
480+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
481+
s.settimeout(cls._DNS_CONNECT_TIMEOUT)
482+
s.connect((dns_host, 80))
483+
preferred_ip = s.getsockname()[0]
484+
break
485+
except Exception:
486+
continue
481487

482488
if preferred_ip:
483489
if preferred_ip in network_ips:

0 commit comments

Comments
 (0)