export type SQLiteConfig = { password?: string; encryption?: boolean; locateFile?: (filename: string) => string; } export type SQLiteResult = { rows?: any[]; columns?: string[]; changes?: number; lastInsertRowid?: number; error?: string; maps?:Map[] } export type SQLiteExecuteBatchParams = { sql:string, params?:any[] } async function loadScript(url) { return new Promise(res => { var script = document.createElement('script'); script.type = 'text/javascript'; script.src = url; // 当脚本加载完成后,执行回调 script.onload = function () { res() }; // 处理旧版浏览器的onreadystatechange事件 script.onreadystatechange = function () { if (this.readyState === 'loaded' || this.readyState === 'complete') { script.onload(); } }; // 将脚本添加到head中,开始加载 document.head.appendChild(script); }) } async function loadScripts(scripts) { await (async function loadNextScript(i) { if (i < scripts.length) { await loadScript(scripts[i]); await loadNextScript(i + 1) } })(0); } let jsFiles = [ '/static/tmui4xLibs/lib/sql-wasm.js' ]; /** * SQLite数据库操作类 * 提供SQLite数据库的创建、查询、更新等基本操作,支持加密和事务处理 */ export class xSqlite { /** 数据库实例 */ private db: any|null = null; private config: SQLiteConfig; private isInTransaction: boolean = false; private SQL:any|null = null; private newFileName:string = "sqlite" constructor(configs: SQLiteConfig = {}) { let locateFile = filename => `/static/tmui4xLibs/lib/sql-wasm.wasm` this.config = {locateFile,...configs}; this.config.locateFile = this.config.locateFile||locateFile } private _loadSqliteJs(): Promise{ let _this = this; return new Promise((res, rej) => { loadScripts(jsFiles).then(() => { initSqlJs(_this.config).then((SQL) => { _this.SQL = SQL; res(); }).catch(e => rej(e)) }).catch(e => rej(e)) }) } /** * 创建数据库 * @param filename 数据库文件名,如果为null则使用默认名称'sqlite' * @returns Promise 创建的数据库实例 */ async createDb(filename?:string): Promise { let _this = this; this.newFileName = filename||"sqlite" return new Promise((res, rej) => { _this._loadSqliteJs() .then(_=>{ _this.db = new _this.SQL.Database(); // 如果配置了加密 if (this.config.encryption && _this.config.password) { _this.run('PRAGMA key = ?', [_this.config.password]); } }) .catch(_=>{ }) }) } /** * 执行SQL语句 * @param sql SQL语句 * @param params SQL参数数组 * @returns SQLiteResult 执行结果,包含changes(影响行数)和lastInsertRowid(最后插入行ID) */ run(sql: string, params: any[] = []): SQLiteResult { try { if (this.isInTransaction) { return { error: 'Transaction already in progress' }; } const result = this.db.run(sql, params); return { changes: result?.changes, lastInsertRowid: result?.lastInsertRowid }; } catch (error) { return { error: error.message }; } } /** * 查询数据 * @param sql 查询SQL语句 * @param params 查询参数数组 * @returns SQLiteResult 查询结果,包含rows(数据行)和columns(列名) */ query(sql: string, params: any[] = []): SQLiteResult { try { if (this.isInTransaction) { return { error: 'Transaction already in progress' }; } const result = this.db.exec(sql, params); const maps = [] as Map[]; const rows = result?.[0]?.values || [] ; const columns = result?.[0]?.columns || [] ; if(rows.length>0&&columns.length>0){ rows.forEach(el=>{ let newmap = new Map(); columns.forEach((key,index)=>{ newmap.set(key,el[index]) }) maps.push(newmap) }) } return { rows: rows, columns: columns, maps }; } catch (error) { return { error: error.message }; } } /** * 插入数据 * @param table 表名 * @param data 要插入的数据对象 * @returns SQLiteResult 插入结果 */ insert(table: string, data: object): SQLiteResult { const keys = Object.keys(data); const values = Object.values(data); const sql = `INSERT INTO ${table} (${keys.join(',')}) VALUES (${keys.map(() => '?').join(',')})`; return this.run(sql, values); } /** * 更新数据 * @param table 表名 * @param data 要更新的数据对象 * @param where WHERE条件语句 * @param params WHERE条件参数数组 * @returns SQLiteResult 更新结果 */ update(table: string, data: object, where: string, params: any[] = []): SQLiteResult { const sets = Object.keys(data).map(key => `${key} = ?`).join(','); const values = [...Object.values(data), ...params]; const sql = `UPDATE ${table} SET ${sets} WHERE ${where}`; return this.run(sql, values); } /** * 删除数据 * @param table 表名 * @param where WHERE条件语句 * @param params WHERE条件参数数组 * @returns SQLiteResult 删除结果 */ delete(table: string, where: string, params: any[] = []): SQLiteResult { const sql = `DELETE FROM ${table} WHERE ${where}`; return this.run(sql, params); } /** * 保存数据库到本地存储 * @param filename 保存的文件名,可选,默认为'sqlite' * @returns SQLiteResult 保存结果 */ saveLocal(filename?: string): SQLiteResult { if (this.db==null) throw new Error('Database not initialized'); let fname = filename??'sqlite' const data = this.db.export(); this.newFileName = fname; // const blob = new Blob([data], { type: 'application/octet-stream' }); // const url = window.URL.createObjectURL(blob); const base64String = btoa(String.fromCharCode(...data)); // const a = document.createElement('a'); // a.href = url; // a.download = filename; // document.body.appendChild(a); // a.click(); // a.remove(); // window.URL.revokeObjectURL(url); if(base64String.length>5*1024*1024){ console.error("数据过大") return {error:"数据过大"}; } try { uni.setStorageSync(fname,base64String) } catch (error) { return {error:"数据过大"}; } } async loadLocal(filename: string): Promise { this.newFileName = filename; const data = uni.getStorageSync(filename) if(data==null){ console.error("没有数据库") return Promise.reject({error:"没有数据库"}) } try { const buffer = Uint8Array.from(atob(data), c => c.charCodeAt(0)); await this._loadSqliteJs() this.loadFromBuffer(buffer); return Promise.resolve({}) } catch (error) { return Promise.reject({error:"数据库损坏"}) } return Promise.reject({error:"没有数据库"}) } /** * 从文件加载数据库 * @param file 要加载的数据库文件 * @returns Promise 加载完成的Promise */ async loadFromFile(file: File): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = async event => { try { const uInt8Array = new Uint8Array(event.target.result as ArrayBuffer); await this.loadFromBuffer(uInt8Array); resolve(); } catch (error) { reject(error); } }; reader.onerror = () => reject(reader.error); reader.readAsArrayBuffer(file); }); } /** * 从远程URL加载数据库 * @param url 数据库文件的URL地址 * @returns Promise 加载完成的Promise */ async loadFromUrl(url: string): Promise { const response = await fetch(url); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const buffer = await response.arrayBuffer(); await this.loadFromBuffer(new Uint8Array(buffer)); } // 从Buffer加载数据库 loadFromBuffer(buffer: Uint8Array): void{ if (this.SQL==null) throw new Error('Database not initialized'); this.db = new this.SQL.Database(buffer); // 如果配置了加密 if (this.config.encryption && this.config.password) { this.run('PRAGMA key = ?', [this.config.password]); } } /** * 检查表是否存在 * @param tableName 表名 * @returns boolean 表存在返回true,不存在返回false */ tableExists(tableName: string): boolean { const result = this.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?", [tableName]); return result.rows && result.rows.length > 0; } /** * 创建数据表 * @param tableName 表名 * @param columns 列定义对象,key为列名,value为列类型定义 * @returns SQLiteResult 创建结果 */ createTable(tableName: string, columns: UTSJSONObject): SQLiteResult { const dataMap = new Map(); for(const key in columns){ const item = columns.getString(key)!; dataMap.set(key,item) } const columnDefinitions = Array.from(dataMap.entries()) .map(([name, type]) => `${name} ${type}`) .join(','); const sql = `CREATE TABLE IF NOT EXISTS ${tableName} (${columnDefinitions})`; return this.run(sql); } /** * 删除数据表 * @param tableName 要删除的表名 * @returns SQLiteResult 删除结果 */ dropTable(tableName: string): SQLiteResult { const sql = `DROP TABLE IF EXISTS ${tableName}`; return this.run(sql); } /** * 关闭数据库连接 * 释放数据库资源,关闭后需要重新创建才能使用 */ close(): void { if (this.db) { this.db.close(); this.db = null; } } /** * 开始事务 * 开始一个新的事务,在提交或回滚之前,所有操作都在事务内 * @returns SQLiteResult 事务开始结果 */ beginTransaction(): SQLiteResult{ if (this.isInTransaction) { return { error: 'Transaction already in progress' }; } const result = this.run('BEGIN TRANSACTION'); if (!result.error) { this.isInTransaction = true; } return result; } /** * 提交事务 * 提交当前事务的所有操作 * @returns SQLiteResult 事务提交结果 */ commit(): SQLiteResult { if (!this.isInTransaction) { return { error: 'No transaction in progress' }; } const result = this.run('COMMIT'); if (!result.error) { this.isInTransaction = false; } return result; } /** * 回滚事务 * 撤销当前事务中的所有操作 * @returns SQLiteResult 事务回滚结果 */ rollback(): SQLiteResult { if (!this.isInTransaction) { return { error: 'No transaction in progress' }; } const result = this.run('ROLLBACK'); if (!result.error) { this.isInTransaction = false; } return result; } /** * 批量执行SQL语句 * @param statements SQL语句数组,每个元素包含sql和params * @returns SQLiteResult[] 每个语句的执行结果数组 */ executeBatch(statements: SQLiteExecuteBatchParams[]): SQLiteResult[] { if (this.isInTransaction) { return []; } const results: SQLiteResult[] = []; let hasError = false; // 自动使用事务包装批量操作 const wasInTransaction = this.isInTransaction; if (!wasInTransaction) { this.beginTransaction(); } try { for (const stmt of statements) { const result = this.run(stmt.sql, stmt?.params??[]); results.push(result); if (result.error) { hasError = true; break; } } // 如果有错误并且是我们开始的事务,则回滚 if (hasError && !wasInTransaction) { this.rollback(); } else if (!wasInTransaction) { // 如果是我们开始的事务且没有错误,则提交 this.commit(); } } catch (error) { // 发生异常且是我们开始的事务,则回滚 if (!wasInTransaction) { this.rollback(); } results.push({ error: error.message }); } return results; } /** * 获取数据库文件路径 * 获取当前数据库文件的完整路径,获取前会备份到缓存目录 * @returns string|null 数据库文件路径,如果数据库未初始化则返回null */ getDatabasePath():Uint8Array|null{ if (this.db==null){ console.error('Database not initialized') return null } const data = this.db.export(); // const blob = new Blob([data], { type: 'application/octet-stream' }); // const url = window.URL.createObjectURL(blob); return data; } /** * 当前数据库的目录 */ setDefaultDirectory(directory: string){ if (this.db==null){ console.error('Database not initialized') return } } /** * 设置数据库密码 * 空值或者null即删除密码 */ setPassword(password: string|null = null){ if (this.db==null){ console.error('Database not initialized') return } this.config.password = password this.config.encryption = password!=null&&!!password } }