1、配置管理-数据库连接失败时显示明确的提示信息

2、数据库结构检测和初始化功能实现
master
Lxy 1 month ago
parent 74f591ee78
commit 7a2094b738

@ -194,6 +194,7 @@ async def detect_all_missing_data(
current_user: User = Depends(get_current_user)
):
"""一键检测所有数据的缺失情况"""
try:
service = CacheService(db)
start = parse_date(request.start_date)
end = parse_date(request.end_date)
@ -207,6 +208,12 @@ async def detect_all_missing_data(
)
return ResponseModel(data=result)
except ValueError as e:
return ResponseModel(code=400, message=f"参数错误: {str(e)}")
except RuntimeError as e:
return ResponseModel(code=500, message=f"服务错误: {str(e)}")
except Exception as e:
return ResponseModel(code=500, message=f"检测失败: {str(e)}")
@router.post("/detect-missing", response_model=ResponseModel)

@ -227,10 +227,11 @@ async def test_system_connection(
result = {
"database": False,
"redis": False
"redis": False,
"database_error": None,
"redis_error": None
}
# 测试数据库连接
if "database" in configs:
try:
engine = sqlalchemy.create_engine(
@ -239,17 +240,26 @@ async def test_system_connection(
)
with engine.connect() as conn:
result["database"] = True
except sqlalchemy.exc.OperationalError as e:
result["database_error"] = f"数据库连接错误: {str(e)}"
except sqlalchemy.exc.ProgrammingError as e:
result["database_error"] = f"数据库配置错误: {str(e)}"
except sqlalchemy.exc.AuthenticationFailed as e:
result["database_error"] = f"数据库认证失败: 用户名或密码错误"
except Exception as e:
pass
result["database_error"] = f"数据库连接失败: {str(e)}"
# 测试Redis连接
if "redis" in configs:
try:
redis_client = redis.from_url(configs["redis"])
redis_client.ping()
result["redis"] = True
except redis.exceptions.ConnectionError as e:
result["redis_error"] = f"Redis连接错误: 无法连接到Redis服务器请检查地址和端口"
except redis.exceptions.AuthenticationError as e:
result["redis_error"] = f"Redis认证失败: 密码错误"
except Exception as e:
pass
result["redis_error"] = f"Redis连接失败: {str(e)}"
return ResponseModel(data=result)
@ -283,19 +293,26 @@ async def check_database_structure(
inspector = inspect(engine)
existing_tables = inspector.get_table_names()
# 检查必要的表是否存在
required_tables = [
'users',
'sdk_configs',
'system_configs',
'stock_info',
'stock_kline_daily',
'stock_kline_minute',
'future_kline_daily',
'future_kline_minute',
'cache_tasks',
'stock_kline_min',
'stock_basic',
'index_basic',
'index_trade'
'index_trade',
'future_info',
'future_kline_daily',
'future_kline_min',
'realtime_snapshot',
'finance_balance_sheet',
'finance_cash_flow',
'finance_income',
'cache_tasks',
'cache_task_details',
'api_test_logs'
]
missing_tables = [table for table in required_tables if table not in existing_tables]
@ -304,7 +321,8 @@ async def check_database_structure(
return ResponseModel(data={
"complete": complete,
"missing_tables": missing_tables,
"existing_tables": existing_tables
"existing_tables": existing_tables,
"required_tables": required_tables
})
except Exception as e:
return ResponseModel(

@ -101,6 +101,7 @@ async def get_stock_kline_chart(
current_user: User = Depends(get_current_user)
):
"""获取股票K线图数据ECharts格式含基础信息"""
try:
service = StockService(db)
start = parse_date(start_date)
end = parse_date(end_date)
@ -122,6 +123,12 @@ async def get_stock_kline_chart(
}
return ResponseModel(data=chart_data)
except ValueError as e:
return ResponseModel(code=400, message=f"参数错误: {str(e)}")
except RuntimeError as e:
return ResponseModel(code=500, message=f"服务错误: {str(e)}")
except Exception as e:
return ResponseModel(code=500, message=f"查询失败: {str(e)}")
@router.get("/basic/{code}", response_model=ResponseModel)

@ -61,8 +61,12 @@ def init_db() -> None:
"""初始化数据库表"""
from app.models import user, config, stock, future, realtime, finance, cache, test
from app.models.user import User
from app.models.stock_basic import StockBasic, IndexBasic, IndexTrade
from app.core.security import get_password_hash
print("开始创建数据库表...")
Base.metadata.create_all(bind=engine)
print("数据库表创建完成")
db = SessionLocal()
try:

@ -72,7 +72,3 @@ class IndexTrade(Base):
updated_at = Column(DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow)
index = relationship("IndexBasic", back_populates="trades")
__table_args__ = (
{'unique_constraint': None},
)

@ -330,12 +330,15 @@ const handleDetectAll = async () => {
if (res.data.status === 'completed') {
ElMessage.success(`检测完成:完整${res.data.complete_count}个,缺失${res.data.missing_count}个,错误${res.data.error_count}`)
} else if (res.data.status === 'failed') {
ElMessage.error(`检测失败:${res.data.error_message}`)
ElMessage.error(`检测失败:${res.data.error_message || '未知错误'}`)
}
} else {
ElMessage.error(res.message || '检测失败,请稍后重试')
}
} catch (error) {
} catch (error: any) {
console.error(error)
ElMessage.error('检测失败')
const errorMsg = error.response?.data?.message || error.message || '网络请求失败'
ElMessage.error(`检测失败: ${errorMsg}`)
} finally {
detectingAll.value = false
}
@ -365,10 +368,13 @@ const handleCacheAll = async () => {
}
ElMessage.success(`缓存任务已启动,共${res.data.total_count}个代码`)
pollTaskProgress(res.data.task_id)
} else {
ElMessage.error(res.message || '缓存任务启动失败')
}
} catch (error) {
} catch (error: any) {
console.error(error)
ElMessage.error('缓存失败')
const errorMsg = error.response?.data?.message || error.message || '网络请求失败'
ElMessage.error(`缓存失败: ${errorMsg}`)
} finally {
cachingAll.value = false
}
@ -404,10 +410,13 @@ const handleFillMissing = async () => {
pollTaskProgress(res.data.task_id)
}
}
} else {
ElMessage.error(res.message || '补齐任务启动失败')
}
} catch (error) {
} catch (error: any) {
console.error(error)
ElMessage.error('补齐失败')
const errorMsg = error.response?.data?.message || error.message || '网络请求失败'
ElMessage.error(`补齐失败: ${errorMsg}`)
} finally {
fillingMissing.value = false
}
@ -439,9 +448,17 @@ const handleDetect = async () => {
: 0,
details: item.missing_dates
}))
if (batchDetectResult.value.length === 0) {
ElMessage.success('所有代码数据完整,无缺失')
}
} else {
ElMessage.error(res.message || '检测失败,请稍后重试')
}
} catch (error) {
} catch (error: any) {
console.error(error)
const errorMsg = error.response?.data?.message || error.message || '网络请求失败'
ElMessage.error(`检测失败: ${errorMsg}`)
} finally {
detecting.value = false
}
@ -453,16 +470,23 @@ const handleCache = async () => {
caching.value = true
try {
await batchCacheData({
const res: any = await batchCacheData({
security_type: form.securityType,
period_type: form.periodType,
start_date: form.startDate,
end_date: form.endDate,
code_list: codes
})
if (res.data) {
ElMessage.success('缓存任务已启动')
} catch (error) {
} else {
ElMessage.error(res.message || '缓存任务启动失败')
}
} catch (error: any) {
console.error(error)
const errorMsg = error.response?.data?.message || error.message || '网络请求失败'
ElMessage.error(`缓存失败: ${errorMsg}`)
} finally {
caching.value = false
}

@ -306,9 +306,17 @@ const buildDatabaseUrl = () => {
case 'sqlite':
return `sqlite:///${systemForm.dbPath}`
case 'postgresql':
if (systemForm.dbPassword) {
return `postgresql://${systemForm.dbUsername}:${systemForm.dbPassword}@${systemForm.dbHost}:${systemForm.dbPort}/${systemForm.dbName}`
} else {
return `postgresql://${systemForm.dbUsername}@${systemForm.dbHost}:${systemForm.dbPort}/${systemForm.dbName}`
}
case 'mysql':
if (systemForm.dbPassword) {
return `mysql://${systemForm.dbUsername}:${systemForm.dbPassword}@${systemForm.dbHost}:${systemForm.dbPort}/${systemForm.dbName}`
} else {
return `mysql://${systemForm.dbUsername}@${systemForm.dbHost}:${systemForm.dbPort}/${systemForm.dbName}`
}
default:
return 'sqlite:///./amazing_data.db'
}
@ -320,19 +328,49 @@ const parseDatabaseUrl = (url: string) => {
systemForm.dbPath = url.replace('sqlite:///', '')
} else if (url.startsWith('postgresql://')) {
systemForm.dbType = 'postgresql'
try {
const urlPart = url.replace('postgresql://', '')
const parts = urlPart.split('@')
if (parts.length === 2) {
const userPass = parts[0].split(':')
systemForm.dbUsername = userPass[0] || 'postgres'
systemForm.dbPassword = userPass.length > 1 ? userPass[1] : ''
const hostPortDb = parts[1].split('/')
systemForm.dbName = hostPortDb.length > 1 ? hostPortDb[1] : 'amazing_data'
const hostPort = hostPortDb[0].split(':')
systemForm.dbHost = hostPort[0] || 'localhost'
systemForm.dbPort = hostPort.length > 1 ? parseInt(hostPort[1]) : 5432
}
} catch (e) {
systemForm.dbHost = 'localhost'
systemForm.dbPort = 5432
systemForm.dbUsername = 'postgres'
systemForm.dbPassword = ''
systemForm.dbName = 'amazing_data'
}
} else if (url.startsWith('mysql://')) {
systemForm.dbType = 'mysql'
try {
const urlPart = url.replace('mysql://', '')
const parts = urlPart.split('@')
if (parts.length === 2) {
const userPass = parts[0].split(':')
systemForm.dbUsername = userPass[0] || 'root'
systemForm.dbPassword = userPass.length > 1 ? userPass[1] : ''
const hostPortDb = parts[1].split('/')
systemForm.dbName = hostPortDb.length > 1 ? hostPortDb[1] : 'amazing_data'
const hostPort = hostPortDb[0].split(':')
systemForm.dbHost = hostPort[0] || 'localhost'
systemForm.dbPort = hostPort.length > 1 ? parseInt(hostPort[1]) : 3306
}
} catch (e) {
systemForm.dbHost = 'localhost'
systemForm.dbPort = 3306
systemForm.dbUsername = 'root'
systemForm.dbPassword = ''
systemForm.dbName = 'amazing_data'
}
}
}
const fetchSystemConfigs = async () => {
@ -467,12 +505,16 @@ const handleTestConnection = async () => {
if (!valid) return
testingConnection.value = true
dbStructureStatus.value = null
showInitButton.value = false
try {
const databaseUrl = buildDatabaseUrl()
const res: any = await testSystemConnection({
database: databaseUrl,
redis: systemForm.redis
})
if (res.data?.database) {
ElMessage.success('数据库连接成功')
const structureRes: any = await checkDatabaseStructure()
@ -483,20 +525,25 @@ const handleTestConnection = async () => {
} else {
dbStructureStatus.value = 'incomplete'
showInitButton.value = true
ElMessage.warning('数据结构不完整,需要初始化')
const missingTables = structureRes.data?.missing_tables || []
ElMessage.warning(`数据结构不完整,缺少表: ${missingTables.join(', ')}`)
}
} else {
ElMessage.error('数据库连接失败')
const dbErrorMsg = res.data?.database_error || res.message || '未知错误'
ElMessage.error(`数据库连接失败: ${dbErrorMsg}`)
dbStructureStatus.value = null
showInitButton.value = false
}
if (res.data?.redis) {
ElMessage.success('Redis连接成功')
} else {
ElMessage.error('Redis连接失败')
const redisErrorMsg = res.data?.redis_error || '请检查Redis服务是否启动'
ElMessage.error(`Redis连接失败: ${redisErrorMsg}`)
}
} catch (error) {
ElMessage.error('检测连接失败')
} catch (error: any) {
const errorMsg = error.response?.data?.message || error.message || '网络请求失败'
ElMessage.error(`检测连接失败: ${errorMsg}`)
dbStructureStatus.value = null
showInitButton.value = false
} finally {

@ -167,6 +167,9 @@ const handleQuery = async () => {
chartData.values = res.data.values || []
chartData.volumes = res.data.volumes || []
if (chartData.categoryData.length === 0) {
ElMessage.warning('未查询到K线数据请检查日期范围或股票代码是否正确')
} else {
tableData.value = chartData.categoryData.map((date, index) => ({
date,
open: chartData.values[index]?.[0],
@ -180,9 +183,13 @@ const handleQuery = async () => {
renderChart()
})
}
} catch (error) {
} else {
ElMessage.error(res.message || '查询失败,请稍后重试')
}
} catch (error: any) {
console.error(error)
ElMessage.error('查询失败')
const errorMsg = error.response?.data?.message || error.message || '查询失败,请检查网络连接'
ElMessage.error(`查询失败: ${errorMsg}`)
} finally {
loading.value = false
}

Loading…
Cancel
Save