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) current_user: User = Depends(get_current_user)
): ):
"""一键检测所有数据的缺失情况""" """一键检测所有数据的缺失情况"""
try:
service = CacheService(db) service = CacheService(db)
start = parse_date(request.start_date) start = parse_date(request.start_date)
end = parse_date(request.end_date) end = parse_date(request.end_date)
@ -207,6 +208,12 @@ async def detect_all_missing_data(
) )
return ResponseModel(data=result) 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) @router.post("/detect-missing", response_model=ResponseModel)

@ -227,10 +227,11 @@ async def test_system_connection(
result = { result = {
"database": False, "database": False,
"redis": False "redis": False,
"database_error": None,
"redis_error": None
} }
# 测试数据库连接
if "database" in configs: if "database" in configs:
try: try:
engine = sqlalchemy.create_engine( engine = sqlalchemy.create_engine(
@ -239,17 +240,26 @@ async def test_system_connection(
) )
with engine.connect() as conn: with engine.connect() as conn:
result["database"] = True 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: except Exception as e:
pass result["database_error"] = f"数据库连接失败: {str(e)}"
# 测试Redis连接
if "redis" in configs: if "redis" in configs:
try: try:
redis_client = redis.from_url(configs["redis"]) redis_client = redis.from_url(configs["redis"])
redis_client.ping() redis_client.ping()
result["redis"] = True 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: except Exception as e:
pass result["redis_error"] = f"Redis连接失败: {str(e)}"
return ResponseModel(data=result) return ResponseModel(data=result)
@ -283,19 +293,26 @@ async def check_database_structure(
inspector = inspect(engine) inspector = inspect(engine)
existing_tables = inspector.get_table_names() existing_tables = inspector.get_table_names()
# 检查必要的表是否存在
required_tables = [ required_tables = [
'users', 'users',
'sdk_configs', 'sdk_configs',
'system_configs', 'system_configs',
'stock_info',
'stock_kline_daily', 'stock_kline_daily',
'stock_kline_minute', 'stock_kline_min',
'future_kline_daily',
'future_kline_minute',
'cache_tasks',
'stock_basic', 'stock_basic',
'index_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] 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={ return ResponseModel(data={
"complete": complete, "complete": complete,
"missing_tables": missing_tables, "missing_tables": missing_tables,
"existing_tables": existing_tables "existing_tables": existing_tables,
"required_tables": required_tables
}) })
except Exception as e: except Exception as e:
return ResponseModel( return ResponseModel(

@ -101,6 +101,7 @@ async def get_stock_kline_chart(
current_user: User = Depends(get_current_user) current_user: User = Depends(get_current_user)
): ):
"""获取股票K线图数据ECharts格式含基础信息""" """获取股票K线图数据ECharts格式含基础信息"""
try:
service = StockService(db) service = StockService(db)
start = parse_date(start_date) start = parse_date(start_date)
end = parse_date(end_date) end = parse_date(end_date)
@ -122,6 +123,12 @@ async def get_stock_kline_chart(
} }
return ResponseModel(data=chart_data) 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) @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 import user, config, stock, future, realtime, finance, cache, test
from app.models.user import User from app.models.user import User
from app.models.stock_basic import StockBasic, IndexBasic, IndexTrade
from app.core.security import get_password_hash from app.core.security import get_password_hash
print("开始创建数据库表...")
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)
print("数据库表创建完成")
db = SessionLocal() db = SessionLocal()
try: try:

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

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

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

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

Loading…
Cancel
Save