feat(saved-query): 支持已存查询后端持久化与连接重绑

- 后端新增 saved_queries.json 仓库,保存、导入、删除和重绑统一走 Wails 方法
- 启动时导入旧 lite-db-storage 中的 savedQueries 和连接快照,成功后清理旧字段
- 新增连接指纹匹配,唯一强匹配自动重绑,歧义场景保留为未匹配
- 侧边栏新增未匹配已存查询分组,并支持手动绑定到目标连接
- 前端保存、重命名、删除查询改为后端持久化,并补充浏览器 mock
- 补充后端与前端迁移回归测试
This commit is contained in:
Syngnat
2026-06-15 12:20:57 +08:00
parent 0b9f0448c8
commit eca9601ab0
20 changed files with 1870 additions and 66 deletions

View File

@@ -76,6 +76,8 @@ export function DataSyncPreview(arg1:sync.SyncConfig,arg2:string,arg3:number):Pr
export function DeleteConnection(arg1:string):Promise<void>;
export function DeleteQuery(arg1:string):Promise<void>;
export function DeleteSQLDirectory(arg1:string):Promise<connection.QueryResult>;
export function DeleteSQLFile(arg1:string):Promise<connection.QueryResult>;
@@ -136,8 +138,12 @@ export function GetGlobalProxyConfig():Promise<connection.QueryResult>;
export function GetSavedConnections():Promise<Array<connection.SavedConnectionView>>;
export function GetSavedQueries():Promise<Array<connection.SavedQuery>>;
export function GetSecurityUpdateStatus():Promise<app.SecurityUpdateStatus>;
export function GetUnboundSavedQueries():Promise<Array<connection.SavedQuery>>;
export function ImportConfigFile():Promise<connection.QueryResult>;
export function ImportConnectionsPayload(arg1:string,arg2:string):Promise<Array<connection.SavedConnectionView>>;
@@ -150,6 +156,8 @@ export function ImportLegacyConnections(arg1:Array<connection.SavedConnectionInp
export function ImportLegacyGlobalProxy(arg1:connection.SaveGlobalProxyInput):Promise<connection.GlobalProxyView>;
export function ImportSavedQueries(arg1:connection.SavedQueryImportPayload):Promise<Array<connection.SavedQuery>>;
export function InstallLocalDriverPackage(arg1:string,arg2:string,arg3:string,arg4:string):Promise<connection.QueryResult>;
export function InstallUpdateAndRestart():Promise<connection.QueryResult>;
@@ -216,6 +224,8 @@ export function ReadAppLogTail(arg1:number,arg2:string):Promise<connection.Query
export function ReadSQLFile(arg1:string):Promise<connection.QueryResult>;
export function RebindSavedQuery(arg1:string,arg2:string):Promise<connection.SavedQuery>;
export function RedisConnect(arg1:connection.ConnectionConfig):Promise<connection.QueryResult>;
export function RedisDeleteHashField(arg1:connection.ConnectionConfig,arg2:string,arg3:any):Promise<connection.QueryResult>;
@@ -294,6 +304,8 @@ export function SaveConnection(arg1:connection.SavedConnectionInput):Promise<con
export function SaveGlobalProxy(arg1:connection.SaveGlobalProxyInput):Promise<connection.GlobalProxyView>;
export function SaveQuery(arg1:connection.SavedQuery):Promise<connection.SavedQuery>;
export function SelectCertificateFile(arg1:string,arg2:string):Promise<connection.QueryResult>;
export function SelectDataRootDirectory(arg1:string):Promise<connection.QueryResult>;

View File

@@ -142,6 +142,10 @@ export function DeleteConnection(arg1) {
return window['go']['app']['App']['DeleteConnection'](arg1);
}
export function DeleteQuery(arg1) {
return window['go']['app']['App']['DeleteQuery'](arg1);
}
export function DeleteSQLDirectory(arg1) {
return window['go']['app']['App']['DeleteSQLDirectory'](arg1);
}
@@ -262,10 +266,18 @@ export function GetSavedConnections() {
return window['go']['app']['App']['GetSavedConnections']();
}
export function GetSavedQueries() {
return window['go']['app']['App']['GetSavedQueries']();
}
export function GetSecurityUpdateStatus() {
return window['go']['app']['App']['GetSecurityUpdateStatus']();
}
export function GetUnboundSavedQueries() {
return window['go']['app']['App']['GetUnboundSavedQueries']();
}
export function ImportConfigFile() {
return window['go']['app']['App']['ImportConfigFile']();
}
@@ -290,6 +302,10 @@ export function ImportLegacyGlobalProxy(arg1) {
return window['go']['app']['App']['ImportLegacyGlobalProxy'](arg1);
}
export function ImportSavedQueries(arg1) {
return window['go']['app']['App']['ImportSavedQueries'](arg1);
}
export function InstallLocalDriverPackage(arg1, arg2, arg3, arg4) {
return window['go']['app']['App']['InstallLocalDriverPackage'](arg1, arg2, arg3, arg4);
}
@@ -422,6 +438,10 @@ export function ReadSQLFile(arg1) {
return window['go']['app']['App']['ReadSQLFile'](arg1);
}
export function RebindSavedQuery(arg1, arg2) {
return window['go']['app']['App']['RebindSavedQuery'](arg1, arg2);
}
export function RedisConnect(arg1) {
return window['go']['app']['App']['RedisConnect'](arg1);
}
@@ -578,6 +598,10 @@ export function SaveGlobalProxy(arg1) {
return window['go']['app']['App']['SaveGlobalProxy'](arg1);
}
export function SaveQuery(arg1) {
return window['go']['app']['App']['SaveQuery'](arg1);
}
export function SelectCertificateFile(arg1, arg2) {
return window['go']['app']['App']['SelectCertificateFile'](arg1, arg2);
}

View File

@@ -1199,6 +1199,68 @@ export namespace connection {
return a;
}
}
export class SavedQuery {
id: string;
name: string;
sql: string;
connectionId: string;
dbName: string;
createdAt: number;
connectionFingerprint?: string;
fingerprintVersion?: string;
bindingStatus?: string;
originalConnectionId?: string;
static createFrom(source: any = {}) {
return new SavedQuery(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.id = source["id"];
this.name = source["name"];
this.sql = source["sql"];
this.connectionId = source["connectionId"];
this.dbName = source["dbName"];
this.createdAt = source["createdAt"];
this.connectionFingerprint = source["connectionFingerprint"];
this.fingerprintVersion = source["fingerprintVersion"];
this.bindingStatus = source["bindingStatus"];
this.originalConnectionId = source["originalConnectionId"];
}
}
export class SavedQueryImportPayload {
queries: SavedQuery[];
legacyConnections?: SavedConnectionInput[];
static createFrom(source: any = {}) {
return new SavedQueryImportPayload(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.queries = this.convertValues(source["queries"], SavedQuery);
this.legacyConnections = this.convertValues(source["legacyConnections"], SavedConnectionInput);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
}
@@ -1392,4 +1454,3 @@ export namespace sync {
}
}