chendeben 1 year ago
parent
commit
bd135eae4a

+ 158 - 0
.cursor/rules/cursorrules.mdc

@@ -0,0 +1,158 @@
+---
+description: 
+globs: 
+alwaysApply: true
+---
+# 鸿蒙ArkTS编程规则与最佳实践
+
+## 语法差异规则:ArkTS vs TypeScript
+
+### 1. 空值检查规则
+```typescript
+// ❌ 错误: 未对可能为null的对象执行检查
+this.dbObject.executeSql(sql);
+
+// ✅ 正确: 执行null检查
+if (this.dbObject) {
+  this.dbObject.executeSql(sql);
+}
+```
+
+### 2. 解构赋值规则
+```typescript
+// ❌ 错误: ArkTS不支持解构赋值语法
+for (const [key, value] of Object.entries(obj)) {
+  // 处理逻辑
+}
+
+// ✅ 正确: 使用传统循环方式
+const keys = Object.keys(obj);
+for (let i = 0; i < keys.length; i++) {
+  const key = keys[i];
+  const value = obj[key];
+  // 处理逻辑
+}
+```
+
+### 3. 异步API调用规则
+```typescript
+// ❌ 错误: 回调参数类型不匹配
+dbStore.executeSql(sql, (err, result: ResultSet) => {
+  // 处理逻辑
+});
+
+// ✅ 正确: 使用Promise模式
+dbStore.executeSql(sql)
+  .then(() => {
+    // 成功处理
+  })
+  .catch((err: Error) => {
+    // 错误处理
+    console.log(err.message);
+  });
+```
+
+### 4. 计算属性名规则
+```typescript
+// ❌ 错误: 不支持计算属性名语法
+const obj = { [CONSTANT.KEY]: value };
+
+// ✅ 正确: 使用对象属性赋值语法
+const obj = {};
+obj[CONSTANT.KEY] = value;
+```
+
+### 5. 数据类型规则
+```typescript
+// ❌ 错误: 使用any或未指定泛型类型
+const items = new Set();
+const map = new Map();
+
+// ✅ 正确: 明确指定泛型类型
+const items = new Set<string>();
+const map = new Map<string, number>();
+```
+
+### 6. 错误对象类型规则
+```typescript
+// ❌ 错误: 使用隐式any类型的错误对象
+try {
+  // 代码
+} catch (e) {
+  console.log(`错误: ${e}`);
+}
+
+// ✅ 正确: 明确指定错误对象的类型
+try {
+  // 代码
+} catch (e: Error) {
+  console.log(`错误: ${e.message}`);
+}
+```
+
+## 数据库操作最佳实践
+
+### 1. 表结构升级
+```typescript
+// ❌ 错误: 依赖回调处理的表结构升级
+this.rdbStore.executeSql(tableInfoQuery, (err, result) => {
+  // 处理逻辑
+});
+
+// ✅ 正确: 使用Promise模式并独立处理每个列的添加操作
+this.rdbStore.executeSql(tableInfoQuery)
+  .then(() => {
+    // 为每个需要添加的列单独执行ALTER TABLE
+    Object.keys(columnsToAdd).forEach(column => {
+      const type = columnsToAdd[column];
+      this.rdbStore.executeSql(`ALTER TABLE ${tableName} ADD COLUMN ${column} ${type}`)
+        .then(() => { /* 成功处理 */ })
+        .catch((err: Error) => { /* 错误处理 */ });
+    });
+  });
+```
+
+### 2. 资源释放
+```typescript
+// ❌ 错误: 未关闭ResultSet
+this.rdbStore.query(predicates, (resultSet) => {
+  // 处理逻辑
+});
+
+// ✅ 正确: 确保关闭ResultSet
+this.rdbStore.query(predicates, (resultSet) => {
+  try {
+    // 处理逻辑
+  } finally {
+    resultSet.close();
+  }
+});
+```
+
+## 对象字面量规则
+
+### 1. 复杂对象初始化
+```typescript
+// ❌ 错误: 不支持复杂对象字面量初始化
+const config = {
+  complex: {
+    nested: {
+      value: someValue
+    }
+  }
+};
+
+// ✅ 正确: 分步创建复杂对象
+const config = {};
+config.complex = {};
+config.complex.nested = {};
+config.complex.nested.value = someValue;
+```
+
+## 命名规范
+
+- 类名: PascalCase (例如 MediaTable)
+- 方法名: camelCase (例如 queryByParentPath)
+- 常量: UPPER_SNAKE_CASE (例如 DB_COLUMNS.FILE_PATH)
+- 私有属性: _camelCase (例如 _dbStore)
+

+ 98 - 44
entry/src/main/ets/common/util/MediaTable.ets

@@ -5,6 +5,57 @@ import Logger from './Logger';
 import RdbUtils from './RdbUtils';
 import { Utility } from './Utility';
 
+/**
+ * 数据库字段常量接口定义
+ */
+interface DBColumnsInterface {
+  ID: string;
+  NAME: string;
+  FILE_PATH: string;
+  TYPE: string;
+  VIDEO_SIZE: string;
+  C_TIME: string;
+  PARENT_PATH: string;
+  IS_FAV: string;
+  PIXEL_MAP_PATH: string;
+  ARTIST: string;
+  ALBUM: string;
+  FILE_NAME: string;
+  SIZE: string;
+  DURATION: string;
+  MIME_TYPE: string;
+  TRACK_COUNT: string;
+  SAMPLE_RATE: string;
+  LAST_PLAYED_STR: string;
+  PLAY_COUNT: string;
+  LYRIC_CONTENT: string;
+}
+
+/**
+ * 数据库字段常量,避免硬编码
+ */
+const DB_COLUMNS: DBColumnsInterface = {
+  ID: 'id',
+  NAME: 'name', 
+  FILE_PATH: 'filePath',
+  TYPE: 'mtype',
+  VIDEO_SIZE: 'videoSize',
+  C_TIME: 'cTime',
+  PARENT_PATH: 'parentPath',
+  IS_FAV: 'isFav',
+  PIXEL_MAP_PATH: 'pixelMapPath',
+  ARTIST: 'artist',
+  ALBUM: 'album',
+  FILE_NAME: 'fileName',
+  SIZE: 'size',
+  DURATION: 'duration',
+  MIME_TYPE: 'mimeType',
+  TRACK_COUNT: 'trackCount',
+  SAMPLE_RATE: 'sampleRate',
+  LAST_PLAYED_STR: 'lastPlayedStr',
+  PLAY_COUNT: 'playCount',
+  LYRIC_CONTENT: 'lyricContent'
+};
 
 export default class MediaTable {
   private accountTable = new RdbUtils(RdbUtils.MEDIA_TABLE.tableName, RdbUtils.MEDIA_TABLE.sqlCreate,
@@ -352,60 +403,63 @@ export default class MediaTable {
     const items: VideoItem[] = [];
 
     try {
-      // 逆向遍历方案(规避鸿蒙API特性)
-      while (resultSet.goToNextRow())  {  // 自动边界检查
-        const item = this.buildVideoItem(resultSet);
-        // let id = resultSet.getString(resultSet.getColumnIndex('id'));
-        // let name = resultSet.getString(resultSet.getColumnIndex('name'));
-        // let filePath  = resultSet.getString(resultSet.getColumnIndex('filePath'));
-        // let type  = resultSet.getDouble(resultSet.getColumnIndex('mtype'));
-        // let videoSize  = resultSet.getDouble(resultSet.getColumnIndex('videoSize'));
-        // let cTime = resultSet.getString(resultSet.getColumnIndex('cTime'));
-        // let size = resultSet.getString(resultSet.getColumnIndex('size'));
-        // let parentPath = resultSet.getString(resultSet.getColumnIndex('parentPath'));
-        //
-        // let pixelMapToString = resultSet.getString(resultSet.getColumnIndex('pixelMapToString'));
-        // let artist  = resultSet.getString(resultSet.getColumnIndex('artist'));
-        // let album  = resultSet.getString(resultSet.getColumnIndex('album'));
-        // let fileName  = resultSet.getString(resultSet.getColumnIndex('fileName'));
-        // let item = new VideoItem(name, id, filePath, type, videoSize, cTime,
-        //   undefined,size,pixelMapToString,artist,album,fileName);
-        items.push(item);
+      // 检查结果集是否有效
+      if (resultSet && resultSet.rowCount > 0) {
+        while (resultSet.goToNextRow()) {
+          const item = this.buildVideoItem(resultSet);
+          items.push(item);
+        }
       }
-      // 释放数据集的内存
-      resultSet.close();
     } catch (err) {
-      Logger.error(` onecold testtag parseResultSetToVideoItems: ${err.code} - ${err.message}`);
+      Logger.error(`解析结果集出错: ${err.message}`);
+    } finally {
+      // 确保结果集被关闭
+      if (resultSet) {
+        resultSet.close();
+      }
     }
 
-
     return items;
   }
 
   private buildVideoItem(rs: relationalStore.ResultSet): VideoItem {
     // 添加空值保护
-    const safeGet = (col: string) => rs.getColumnIndex(col)  >= 0 ? rs.getString(rs.getColumnIndex(col))  : '';
-
-    let item =  new VideoItem(
-      safeGet('name'),
-      safeGet('id'),
-      safeGet('filePath'),
-      rs.getDouble(rs.getColumnIndex('mtype'))  || 0,
-      rs.getDouble(rs.getColumnIndex('videoSize'))  || 0,safeGet('cTime'),undefined,safeGet('size'),
-      safeGet('pixelMapPath'),safeGet('artist'),
-      safeGet('album'),safeGet('fileName')
+    const safeGet = (col: string) => {
+      const index = rs.getColumnIndex(col);
+      return index >= 0 ? rs.getString(index) || '' : '';
+    };
+    
+    const safeGetNumber = (col: string) => {
+      const index = rs.getColumnIndex(col);
+      return index >= 0 ? rs.getDouble(index) || 0 : 0;
+    };
+
+    let item = new VideoItem(
+      safeGet(DB_COLUMNS.NAME),
+      safeGet(DB_COLUMNS.ID),
+      safeGet(DB_COLUMNS.FILE_PATH),
+      safeGetNumber(DB_COLUMNS.TYPE),
+      safeGetNumber(DB_COLUMNS.VIDEO_SIZE),
+      safeGet(DB_COLUMNS.C_TIME),
+      undefined,
+      safeGet(DB_COLUMNS.SIZE),
+      safeGet(DB_COLUMNS.PIXEL_MAP_PATH),
+      safeGet(DB_COLUMNS.ARTIST),
+      safeGet(DB_COLUMNS.ALBUM),
+      safeGet(DB_COLUMNS.FILE_NAME)
     );
-    item.isFav = rs.getDouble(rs.getColumnIndex('isFav'));
-
-    item.duration = safeGet('duration');
-    item.mimeType = safeGet('mimeType');
-    item.trackCount =  safeGet('trackCount');
-    item.sampleRate = safeGet('sampleRate');
-    item.lastPlayedStr =safeGet('lastPlayedStr');
-    // item.playCount = rs.getDouble(rs.getColumnIndex('playCount'));
-    item.lyricContent =  safeGet('lyricContent')
-
-    return item
+    
+    // 设置额外属性,添加安全检查
+    item.isFav = safeGetNumber(DB_COLUMNS.IS_FAV);
+    item.duration = safeGet(DB_COLUMNS.DURATION);
+    item.mimeType = safeGet(DB_COLUMNS.MIME_TYPE);
+    item.trackCount = safeGet(DB_COLUMNS.TRACK_COUNT);
+    item.sampleRate = safeGet(DB_COLUMNS.SAMPLE_RATE);
+    item.lastPlayedStr = safeGet(DB_COLUMNS.LAST_PLAYED_STR);
+    item.playCount = safeGetNumber(DB_COLUMNS.PLAY_COUNT);
+    item.lyricContent = safeGet(DB_COLUMNS.LYRIC_CONTENT);
+
+    return item;
   }
 
 

+ 97 - 36
entry/src/main/ets/common/util/RdbUtils.ets

@@ -1,4 +1,3 @@
-
 import relationalStore from '@ohos.data.relationalStore';
 import { LogUtil, StrUtil } from '@pura/harmony-utils';
 import Logger from './Logger';
@@ -52,10 +51,16 @@ export default class RdbUtils {
       '        parentPath TEXT,\n' +
       '        isFav INTEGER,\n' +
       '        pixelMapPath TEXT,\n' +
-      '        pixelMapToString TEXT' +
-
+      '        pixelMapToString TEXT,\n' +
+      '        duration TEXT,\n' +
+      '        sampleRate TEXT,\n' +
+      '        playCount INTEGER DEFAULT 0,\n' +
+      '        lastPlayedStr TEXT,\n' +
+      '        trackCount TEXT,\n' +
+      '        lyricContent TEXT,\n' +
+      '        mimeType TEXT' +
       ')',
-    columns: ['id', 'name', 'filePath','mtype', 'videoSize', 'cTime','size', 'artist', 'album','fileName','parentPath','isFav','pixelMapPath','pixelMapToString']
+    columns: ['id', 'name', 'filePath','mtype', 'videoSize', 'cTime','size', 'artist', 'album','fileName','parentPath','isFav','pixelMapPath','pixelMapToString', 'duration', 'mimeType', 'trackCount', 'sampleRate', 'lastPlayedStr', 'playCount', 'lyricContent']
   };
 
   constructor(tableName: string, sqlCreateTable: string, columns: Array<string>) {
@@ -77,46 +82,95 @@ export default class RdbUtils {
       callback();
       return
     }
-    // let context: Context = getContext(this) as Context;
+    
     relationalStore.getRdbStore(context, RdbUtils.STORE_CONFIG, (err, rdb) => {
       if (err) {
         Logger.error(RdbUtils.RDB_TAG, `gerRdbStore() failed, err: ${err}`);
         return;
       }
       this.rdbStore = rdb;
+      
+      // 先创建表(如果不存在)
       this.rdbStore.executeSql(this.sqlCreateTable);
-      if (this.rdbStore.version  == 0) {
-          // 升级到版本1,添加列
-          // ✅ SQLite要求每列单独执行ALTER TABLE
-          const alterColumns = [
-            "ALTER TABLE mediaTable ADD COLUMN duration TEXT",
-            "ALTER TABLE mediaTable ADD COLUMN sampleRate TEXT",
-            "ALTER TABLE mediaTable ADD COLUMN playCount INTEGER DEFAULT 0",
-            "ALTER TABLE mediaTable ADD COLUMN lastPlayedStr TEXT",
-            "ALTER TABLE mediaTable ADD COLUMN trackCount TEXT",
-            "ALTER TABLE mediaTable ADD COLUMN lyricContent TEXT",
-            "ALTER TABLE mediaTable ADD COLUMN mimeType TEXT"
-          ];
-        try {
-          // 逐列执行添加
-          alterColumns.forEach(sql  => {
-            if(this.rdbStore)
-              this.rdbStore.executeSql(sql);
-          });
-          this.rdbStore.version = 1
-          LogUtil.info('onecold Upgrade  database from version 0 to 1 success.');
-        } catch (e) {
-          Logger.error(RdbUtils.RDB_TAG,  `Upgrade database failed: ${e}`);
-          // 注意:升级失败,可能需要处理,这里我们记录错误,但继续执行回调
-        }
+      
+      // 检查数据库版本并更新列
+      try {
+        // 检查表结构
+        this.checkAndUpdateTableColumns();
+        Logger.info(RdbUtils.RDB_TAG, `数据库表结构检查完成`);
+      } catch (e) {
+        Logger.error(RdbUtils.RDB_TAG, `检查表结构时出错: ${e.message}`);
       }
 
-
-      // Logger.info(RdbUtils.RDB_TAG, 'getRdbStore() finished.');
       callback();
     });
   }
-
+  
+  /**
+   * 检查并更新表列结构
+   * 使用表信息查询和ALTER TABLE添加缺失的列
+   */
+  private checkAndUpdateTableColumns() {
+    if (!this.rdbStore) {
+      Logger.error(RdbUtils.RDB_TAG, `数据库连接未初始化`);
+      return;
+    }
+    
+    try {
+      // 查询表信息获取现有列
+      const tableInfoQuery = `PRAGMA table_info(${this.tableName})`;
+      // 使用Promise模式代替回调
+      this.rdbStore.executeSql(tableInfoQuery)
+        .then(() => {
+          // 定义应该存在的列及其类型
+          const requiredColumns: Record<string, string> = {
+            'duration': 'TEXT',
+            'sampleRate': 'TEXT',
+            'playCount': 'INTEGER DEFAULT 0',
+            'lastPlayedStr': 'TEXT',
+            'trackCount': 'TEXT',
+            'lyricContent': 'TEXT',
+            'mimeType': 'TEXT'
+          };
+          
+          // 逐个添加列,不依赖于检查结果
+          if (this.rdbStore) {
+            // 遍历映射
+            const columnEntries = Object.keys(requiredColumns);
+            for (let i = 0; i < columnEntries.length; i++) {
+              const column = columnEntries[i];
+              const type = requiredColumns[column];
+              
+              const alterSql = `ALTER TABLE ${this.tableName} ADD COLUMN ${column} ${type}`;
+              try {
+                // 对每个列使用Promise模式
+                this.rdbStore.executeSql(alterSql)
+                  .then(() => {
+                    Logger.info(RdbUtils.RDB_TAG, `成功添加列: ${column}`);
+                  })
+                  .catch((alterErr: Error) => {
+                    // 列可能已经存在,这是预期的错误
+                    Logger.info(RdbUtils.RDB_TAG, `列 ${column} 可能已存在: ${alterErr.message}`);
+                  });
+              } catch (e) {
+                Logger.error(RdbUtils.RDB_TAG, `添加列 ${column} 出错: ${e.message}`);
+              }
+            }
+            
+            // 设置数据库版本
+            if (this.rdbStore) {
+              this.rdbStore.version = 1;
+              Logger.info(RdbUtils.RDB_TAG, `数据库升级完成,版本设置为 1`);
+            }
+          }
+        })
+        .catch((err: Error) => {
+          Logger.error(RdbUtils.RDB_TAG, `获取表信息失败: ${err.message}`);
+        });
+    } catch (e) {
+      Logger.error(RdbUtils.RDB_TAG, `检查表结构时出错: ${e.message}`);
+    }
+  }
 
   //检查是否存在相同id的记录,存在则不插入,进一步判断pixelMapPath字段,如果旧值为空而新值不为空,则更新该字段。
   async insertData(data: relationalStore.ValuesBucket, callback: Function = () => {},cover_api?:string) {
@@ -253,15 +307,22 @@ export default class RdbUtils {
       Logger.info(RdbUtils.RDB_TAG, 'query() has no callback!');
       return;
     }
+    
     if (this.rdbStore) {
-      this.rdbStore.query(predicates, this.columns, (err, resultSet) => {
+      // 使用安全的列集合查询
+      // 首先仅查询基本列(确保100%存在)
+      const safeColumns = ['id', 'name', 'filePath', 'mtype', 'videoSize', 'cTime', 
+                           'size', 'artist', 'album', 'fileName', 'parentPath', 
+                           'isFav', 'pixelMapPath', 'pixelMapToString'];
+      
+      this.rdbStore.query(predicates, safeColumns, (err, resultSet) => {
         if (err) {
-          Logger.error(RdbUtils.RDB_TAG, `query() failed, err:  ${err}`);
+          Logger.error(RdbUtils.RDB_TAG, `query() failed, err: ${err}`);
+          callback(null);
           return;
         }
-        // Logger.info(RdbUtils.RDB_TAG, 'query() finished.');
+        
         callback(resultSet);
-        resultSet.close();
       });
     }
   }