Commit 934865dd authored by Data Governance Dev's avatar Data Governance Dev

refactor(web2): store + 兜底配置改为列表结构 — 多连接支持

- dbPresets.js:DB_PRESETS 改为列表结构(每 db_type 是 Connection[])
- useDbConnection.js
  - state.currentType: string → state.currentConnection: {db_type, id}
  - presets[db_type] 从单 dict 改为 Connection[]
  - 新增 currentType computed(便捷暴露 db_type)
  - 新增 setCurrentConnection(db_type, id) 替代 setDbType(type)
  - localStorage 新 key web2.dbConnection.current,序列化用 "db_type::id"
  - API 拉回后校准:当前连接丢失则 fallback 到第一个连接
parent 562894a9
/**
* 数据库连接预设(前端本地兜底)
*
* 来源:web/configs/db_defaults.yaml(同步结构 + display 字段)
* - MySQL / 达梦 (DM) / Oracle 三套配置
* - 改这里 = 改 web/configs/db_defaults.yaml(保持两端结构一致)
* 来源:web2/configs/db_defaults.yaml(与 web1 完全分离,2026-08-17 起)
* - 列表结构:每个 db_type 是 Connection[],可配多套连接
* - 改这里 = 改 web2/configs/db_defaults.yaml
*
* ⚠️ 安全:本文件已入库,password 字段必须留空字符串。
* 真实密码由运行时 GET /api/db-defaults 注入到 store.presets.password。
* 真实密码由运行时 GET /api/db-defaults 注入到 store.presets[*][i].password。
* 后端不通时 password 为空 → 测试连接会失败(这是预期行为,不是 bug)。
*
* 字段说明:
* id 必填,连接级唯一标识(同 db_type 内唯一)
* host / port / user / database / display —— 可入库(无敏感信息)
* password —— 留空
* oracle_client_dir —— 可入库(路径,非凭据)
* password —— 留空
* oracle_client_dir —— 可入库(路径,非凭据)
*/
export const DB_TYPES = [
......@@ -21,41 +22,65 @@ export const DB_TYPES = [
{ value: 'oracle', label: 'Oracle' },
]
/**
* 兜底预设:列表结构,与后端 yaml 一致。
* 当 /api/db-defaults 拉取失败时使用。
*/
export const DB_PRESETS = {
// ── MySQL ──
mysql: {
db_type: 'mysql',
host: '192.168.20.10',
port: 3306,
user: 'root',
password: '',
database: 'smart-build',
display: '智能建造',
oracle_client_dir: '',
},
// ── MySQL(一个 db_type,多套连接) ──
mysql: [
{
id: 'smart-build',
db_type: 'mysql',
host: '192.168.20.10',
port: 3306,
user: 'root',
password: '',
database: 'smart-build',
display: '智能建造',
oracle_client_dir: '',
},
{
id: 'smart-build-backup',
db_type: 'mysql',
host: '192.168.20.11',
port: 3306,
user: 'root',
password: '',
database: 'smart-build',
display: '智能建造(备份)',
oracle_client_dir: '',
},
],
// ── 达梦 (DM) ──
dameng: {
db_type: 'dameng',
host: '192.168.20.24',
port: 5236,
user: 'HGWXZJ',
password: '',
database: 'HGWXZJ',
display: '三金系统',
oracle_client_dir: '',
},
dameng: [
{
id: 'sanjin',
db_type: 'dameng',
host: '192.168.20.24',
port: 5236,
user: 'HGWXZJ',
password: '',
database: 'HGWXZJ',
display: '三金系统',
oracle_client_dir: '',
},
],
// ── Oracle ──
oracle: {
db_type: 'oracle',
host: '192.168.20.196',
port: 1521,
user: 'GJJ80',
password: '',
database: 'ZFGJJ',
display: '黄冈公积金',
oracle_client_dir:
'D:\\Oracle\\instantclient-basic-windows.x64-12.2.0.1.0\\instantclient_12_2',
},
}
oracle: [
{
id: 'huanggang-gjj',
db_type: 'oracle',
host: '192.168.20.196',
port: 1521,
user: 'GJJ80',
password: '',
database: 'ZFGJJ',
display: '黄冈公积金',
oracle_client_dir:
'D:\\Oracle\\instantclient-basic-windows.x64-12.2.0.1.0\\instantclient_12_2',
},
],
}
\ No newline at end of file
......@@ -7,33 +7,78 @@
* 用 DB_PRESETS 常量兜底
*
* 状态:
* currentType 当前选中的 db_type(写到 localStorage)
* presets 三套预设(响应式;API 拉回来后会替换)
* loading 是否正在拉 API
* currentConnection 当前选中的具体连接({db_type, id} | null,写到 localStorage)
* presets 各 db_type 的连接列表(响应式;API 拉回来后会替换)
* loading 是否正在拉 API
*
* 数据结构(2026-08-17 起改为列表):
* presets = {
* mysql: [{id, host, port, ...}, {id, host, port, ...}], // 可多个连接
* dameng: [{id, host, port, ...}],
* oracle: [{id, host, port, ...}],
* }
*
* 设计取舍:
* - 不上 Pinia:当前只一个全局 db 状态,reactive 单例够用
* - 密码不进 localStorage:只存 db_type,需要连接时从 presets 实时取
* - 密码不进 localStorage:只存 db_type+id,需要连接时从 presets 实时取
* - localStorage key 从 STORAGE_KEY(旧:dbConnection.type)改为新 key;
* 旧 key 数据会被忽略(fallback 到第一个连接的 id)
*/
import { reactive, ref, watch, computed } from 'vue'
import { DB_PRESETS as FALLBACK_PRESETS } from '@/config/dbPresets'
const STORAGE_KEY = 'web2.dbConnection.type'
const STORAGE_KEY = 'web2.dbConnection.current'
const API_TIMEOUT_MS = 4000
function _loadInitialType() {
/** 把 {db_type, id} 对象序列化成可写 localStorage 的字符串 */
function _serializeConn(c) {
if (!c) return null
return `${c.db_type}::${c.id}`
}
/** 把 localStorage 里的字符串反序列化成 {db_type, id} */
function _deserializeConn(s) {
if (!s || typeof s !== 'string') return null
const [db_type, id] = s.split('::')
if (!db_type || !id) return null
return { db_type, id }
}
/** 从预设里找指定 {db_type, id} 的连接 */
function _findConn(presetsObj, db_type, id) {
if (!presetsObj || !db_type || !id) return null
const list = presetsObj[db_type]
if (!Array.isArray(list)) return null
return list.find((c) => c && c.id === id) || null
}
/** 默认连接:第一个 db_type 的第一个连接 */
function _defaultConn(presetsObj) {
if (!presetsObj) return null
const firstType = Object.keys(presetsObj)[0]
if (!firstType) return null
const firstConn = presetsObj[firstType]?.[0]
if (!firstConn) return null
return { db_type: firstType, id: firstConn.id }
}
function _loadInitialConn(presetsObj) {
try {
const saved = localStorage.getItem(STORAGE_KEY)
if (saved && FALLBACK_PRESETS[saved]) return saved
const parsed = _deserializeConn(saved)
if (parsed && _findConn(presetsObj, parsed.db_type, parsed.id)) {
return parsed
}
} catch (_) {
/* localStorage 不可用 */
}
return 'mysql'
return _defaultConn(presetsObj)
}
const state = reactive({
currentType: _loadInitialType(),
/** 当前选中的连接 {db_type, id} */
currentConnection: _loadInitialConn(FALLBACK_PRESETS),
/** 是否已经尝试过拉 API(true 后不再重试,避免启动时反复请求) */
apiLoaded: false,
})
......@@ -43,30 +88,39 @@ const presets = ref({ ...FALLBACK_PRESETS })
const loading = ref(false)
watch(
() => state.currentType,
() => state.currentConnection,
(v) => {
try {
localStorage.setItem(STORAGE_KEY, v)
localStorage.setItem(STORAGE_KEY, _serializeConn(v))
} catch (_) {
/* 静默 */
}
},
{ deep: true },
)
/** 当前选中的完整连接信息(响应式) */
const currentConnection = computed(() => presets.value[state.currentType])
const currentConnection = computed(() => {
const c = state.currentConnection
if (!c) return null
return _findConn(presets.value, c.db_type, c.id)
})
/** 当前连接的 db_type(便捷 computed,给 IdCardTool 这类只关心 db_type 的地方用) */
const currentType = computed(() => state.currentConnection?.db_type || null)
/** 切换数据库类型 */
function setDbType(type) {
if (!presets.value[type]) {
console.warn(`[db] 未知数据库类型: ${type}`)
return
/** 切换到指定连接 */
function setCurrentConnection(db_type, id) {
const conn = _findConn(presets.value, db_type, id)
if (!conn) {
console.warn(`[db] 未知连接: ${db_type}::${id}`)
return false
}
state.currentType = type
state.currentConnection = { db_type, id }
return true
}
/**
* 从后端拉 db-defaults,覆盖本地 presets。
/** 从后端拉 db-defaults,覆盖本地 presets。
* 失败/超时 → 保留 FALLBACK_PRESETS 不变,apiLoaded=true 表示「不再重试」。
*/
async function loadPresetsFromApi() {
......@@ -81,7 +135,15 @@ async function loadPresetsFromApi() {
const data = await r.json()
if (data && typeof data === 'object' && Object.keys(data).length > 0) {
presets.value = data
console.log('[db] 已从后端加载 presets:', Object.keys(data))
console.log('[db] 已从后端加载 presets:', {
keys: Object.keys(data),
counts: Object.fromEntries(Object.entries(data).map(([k, v]) => [k, Array.isArray(v) ? v.length : 0])),
})
// 拉回来后校准当前连接:丢了/换预设了就回到默认第一个
const cur = state.currentConnection
if (!cur || !_findConn(data, cur.db_type, cur.id)) {
state.currentConnection = _defaultConn(data)
}
} else {
console.warn('[db] 后端返回空,使用本地兜底预设')
}
......@@ -94,7 +156,7 @@ async function loadPresetsFromApi() {
}
/**
* 测试当前选中的连接(不切换 currentType,只发请求)
* 测试当前选中的连接(不切换 currentConnection,只发请求)
* @returns {Promise<{ok: boolean, message: string}>}
*/
async function testCurrentConnection() {
......@@ -107,9 +169,8 @@ async function testCurrentConnection() {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
// db_type 必须用 state.currentType(已知 db_type 一定有值),
// 不能用 conn.db_type —— 后端 YAML 预设里没有这个字段。
db_type: state.currentType,
// conn 里已含 db_type(后端注入),直接用
db_type: conn.db_type,
host: conn.host,
port: conn.port,
user: conn.user,
......@@ -128,5 +189,14 @@ async function testCurrentConnection() {
}
export function useDbConnection() {
return { state, presets, loading, currentConnection, setDbType, loadPresetsFromApi, testCurrentConnection }
}
return {
state,
presets,
loading,
currentConnection, // 当前具体连接(响应式)
currentType, // 当前连接的 db_type(便捷 computed)
setCurrentConnection, // (db_type, id) => bool
loadPresetsFromApi,
testCurrentConnection,
}
}
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment