Ver código fonte

feat(app): 添加应用崩溃恢复功能并优化网络请求

- 新增 MyAbilityStage 类,实现应用崩溃恢复功能
- 在 ApiElem接口中添加 showLog 字段,用于控制请求日志输出
- 重构 HttpHelper 类,提取公共方法到 HttpHelperX 类
- 优化网络请求方法,增加日志输出和错误处理
- 新增 Base64Helper 和 CharHelper 工具类
徐勤民 5 meses atrás
pai
commit
9a2f82c33c

+ 7 - 0
Index.ets

@@ -4,6 +4,13 @@
 export { ToolsHelper } from './src/main/ets/utils/ToolsHelper'
 export { ToolsHelperForTS } from './src/main/ets/utils/ToolsHelperForTS'
 export { AlgorithmHelper } from './src/main/ets/utils/AlgorithmHelper'
+export { Base64Helper } from './src/main/ets/utils/Base64Helper'
+export { CharHelper } from './src/main/ets/utils/CharHelper'
+export { CrashHelper } from './src/main/ets/utils/CrashHelper'
+export { FileHelper } from './src/main/ets/utils/FileHelper'
+export { PickerHelper } from './src/main/ets/utils/PickerHelper'
+export { StrHelper } from './src/main/ets/utils/StrHelper'
+export { LogHelper } from './src/main/ets/utils/LogHelper'
 
 /**
  * 存储相关

+ 66 - 123
src/main/ets/http/HttpHelper.ts → src/main/ets/http/HttpHelper.ets

@@ -1,27 +1,10 @@
 import { ArrayList, HashMap } from '@kit.ArkTS';
 import http from '@ohos.net.http';
+import { LogHelper } from '../../../../Index';
 import { SZYXLocalStorageHelper } from '../utils/SZYXLocalStorageHelper';
 import { SZYXLocalStorageKeys } from '../utils/SZYXLocalStorageKeys';
-import { ToolsHelperForTS } from '../utils/ToolsHelperForTS';
-
-
-type HttpParamsGet = {
-  url: string
-  query?: Record<string, string> | Object
-  headers?: Record<string, string>
-}
-type HttpParamsPost = {
-  url: string
-  data?: string | Object | ArrayBuffer
-  query?: Record<string, string> | Object
-  headers?: Record<string, string>
-}
-type HttpParamsForm = {
-  url: string
-  data?: Record<string, string> | Object|undefined
-  query?: Record<string, string> | Object
-  headers?: Record<string, string>
-}
+import { HttpHelperX, HttpParamsForm, HttpParamsGet, HttpParamsPost } from './HttpHelperX';
+
 
 export class HttpHelper {
   private static instance: HttpHelper | null = null
@@ -51,7 +34,10 @@ export class HttpHelper {
    * 添加并发白名单
    * @param apiNo
    */
-  public addConcurrent(apiNo: string) {
+  public addConcurrent(apiNo?: string) {
+    if (!apiNo) {
+      return
+    }
     if (this.concurrentList.getIndexOf(apiNo) === -1) {
       this.concurrentList.add(apiNo)
     }
@@ -70,7 +56,7 @@ export class HttpHelper {
    * @param apiNo 请求标识,取消请求或者去重使用|考虑做自动重试使用
    * @returns
    */
-  public postJson<T>(params: HttpParamsPost, apiNo?: string): Promise<T> {
+  public postJson<T>(params: HttpParamsPost, apiNo?: string, showLog?: boolean): Promise<T> {
 
     return new Promise<T>((resolve, reject) => {
 
@@ -79,26 +65,25 @@ export class HttpHelper {
         this.httpHandlerList.get(apiNo ?? params.url).destroy()
         this.httpHandlerList.remove(apiNo ?? params.url)
         SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-        SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength, this.httpHandlerList.length)
+        SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
+          this.httpHandlerList.length)
       }
       let httpRequest = http.createHttp();
 
       if (this.concurrentList.getIndexOf(apiNo ?? params.url) === -1) {
         this.httpHandlerList.set(apiNo ?? params.url, httpRequest)
         SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-        SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength, this.httpHandlerList.length)
+        SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
+          this.httpHandlerList.length)
       }
 
-      const header = {
-        "Content-Type": "application/json;charset=UTF-8",
-        // "Accept": "application/json",
-        ...params.headers
+      const header = HttpHelperX.getHeaders("application/json;charset=UTF-8", params.headers)
+
+      if (showLog) {
+        LogHelper.debug(`postJson:${apiNo}\n`, JSON.stringify(params))
       }
-      console.log('=====>', 'POST:', JSON.stringify(params))
-      // console.log('=====>', '接口请求', JSON.stringify(header))
-      // console.log('=====>', '接口请求', data)
 
-      httpRequest.request(this.getUrl(params.url, params.query), {
+      httpRequest.request(HttpHelperX.getUrl(params.url, params.query), {
         method: http.RequestMethod.POST,
         connectTimeout: 20000,
         readTimeout: 20000,
@@ -106,18 +91,16 @@ export class HttpHelper {
         extraData: params.data
       })
         .then((data: http.HttpResponse) => {
-          console.info(`=====>Result:${data.result as string}(${apiNo})`);
-          // console.info('=====>' + 'code:' + data.responseCode);
-          // console.info('=====>' + 'type:' + JSON.stringify(data.resultType));
-          // console.info('=====>' + 'header:' + JSON.stringify(data.header));
-          // console.info('=====>' + 'cookies:' + data.cookies); // 自API version 8开始支持cookie
-          // console.info('=====>' + 'header.content-Type:' + JSON.stringify(data.header));
-          // console.info('=====>' + 'header.Status-Line:' + JSON.stringify(data.header));
+          if (showLog) {
+            LogHelper.debug(`${apiNo}:\n ${data.result as string}`)
+            LogHelper.print(data)
+          }
 
           if (this.httpHandlerList.hasKey(apiNo ?? params.url)) {
             this.httpHandlerList.remove(apiNo ?? params.url)
             SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-            SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength, this.httpHandlerList.length)
+            SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
+              this.httpHandlerList.length)
           }
           if (data.responseCode === 200) {
             resolve((typeof data.result === 'string' ? JSON.parse(data.result) : data.result) as T)
@@ -128,11 +111,12 @@ export class HttpHelper {
             reject(err)
           }
         }).catch((err: Error) => {
-        console.info('=====>' + 'Error:' + JSON.stringify({ err: err, url: params.url, }));
+        LogHelper.error(JSON.stringify({ err: err, url: params.url, }))
         if (this.httpHandlerList.hasKey(apiNo ?? params.url)) {
           this.httpHandlerList.remove(apiNo ?? params.url)
           SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-          SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength, this.httpHandlerList.length)
+          SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
+            this.httpHandlerList.length)
         }
         if (err.message === 'Failed writing received data to disk/application') {
           reject('cancel')
@@ -151,7 +135,7 @@ export class HttpHelper {
    * @param apiNo 请求标识,取消请求或者去重使用|考虑做自动重试使用
    * @returns
    */
-  public postForm<T>(params: HttpParamsForm, apiNo?: string): Promise<T> {
+  public postForm<T>(params: HttpParamsForm, apiNo?: string, showLog?: boolean): Promise<T> {
 
     return new Promise<T>((resolve, reject) => {
 
@@ -160,47 +144,43 @@ export class HttpHelper {
         this.httpHandlerList.get(apiNo ?? params.url).destroy()
         this.httpHandlerList.remove(apiNo ?? params.url)
         SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-        SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength, this.httpHandlerList.length)
+        SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
+          this.httpHandlerList.length)
       }
       let httpRequest = http.createHttp();
 
       if (this.concurrentList.getIndexOf(apiNo ?? params.url) === -1) {
         this.httpHandlerList.set(apiNo ?? params.url, httpRequest)
         SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-        SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength, this.httpHandlerList.length)
+        SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
+          this.httpHandlerList.length)
       }
 
-      const header = {
-        "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
-        // "Accept": "application/json",
-        ...params.headers
-      }
-      let data = this.getContent(params.data)
+      const header = HttpHelperX.getHeaders("application/x-www-form-urlencoded;charset=UTF-8", params.headers)
+      let data = HttpHelperX.getContent(params.data)
 
-      console.log('=====>', 'POSTForm:', params.url)
-      console.log('=====>', 'POSTForm:', JSON.stringify(header))
-      console.log('=====>', 'POSTForm:', data)
+      if (showLog) {
+        LogHelper.debug(`postForm:${apiNo}\n`, JSON.stringify(params))
+      }
 
-      httpRequest.request(this.getUrl(params.url, params.query), {
+      httpRequest.request(HttpHelperX.getUrl(params.url, params.query), {
         method: http.RequestMethod.POST,
         connectTimeout: 20000,
         readTimeout: 20000,
         header: header,
-        extraData: data?encodeURI(data):undefined
+        extraData: data ? encodeURI(data) : undefined
       })
         .then((data: http.HttpResponse) => {
-          console.info(`=====>Result:${data.result as string}(${apiNo})`);
-          // console.info('=====>' + 'code:' + data.responseCode);
-          // console.info('=====>' + 'type:' + JSON.stringify(data.resultType));
-          // console.info('=====>' + 'header:' + JSON.stringify(data.header));
-          // console.info('=====>' + 'cookies:' + data.cookies); // 自API version 8开始支持cookie
-          // console.info('=====>' + 'header.content-Type:' + JSON.stringify(data.header));
-          // console.info('=====>' + 'header.Status-Line:' + JSON.stringify(data.header));
+          if (showLog) {
+            LogHelper.debug(`${apiNo}:\n ${data.result as string}`)
+            LogHelper.print(data)
+          }
 
           if (this.httpHandlerList.hasKey(apiNo ?? params.url)) {
             this.httpHandlerList.remove(apiNo ?? params.url)
             SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-            SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength, this.httpHandlerList.length)
+            SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
+              this.httpHandlerList.length)
           }
           if (data.responseCode === 200) {
             resolve((typeof data.result === 'string' ? JSON.parse(data.result) : data.result) as T)
@@ -211,11 +191,12 @@ export class HttpHelper {
             reject(err)
           }
         }).catch((err: Error) => {
-        console.info('=====>' + 'Error:' + JSON.stringify({ err: err, url: params.url, }));
+        LogHelper.error(JSON.stringify({ err: err, url: params.url, }))
         if (this.httpHandlerList.hasKey(apiNo ?? params.url)) {
           this.httpHandlerList.remove(apiNo ?? params.url)
           SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-          SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength, this.httpHandlerList.length)
+          SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
+            this.httpHandlerList.length)
         }
         if (err.message === 'Failed writing received data to disk/application') {
           reject('cancel')
@@ -236,7 +217,7 @@ export class HttpHelper {
    * @param apiNo 请求标识,取消请求或者去重使用|考虑做自动重试使用
    * @returns
    */
-  public get<T>(params: HttpParamsGet, apiNo?: string): Promise<T> {
+  public get<T>(params: HttpParamsGet, apiNo?: string, showLog?: boolean): Promise<T> {
 
     return new Promise<T>((resolve, reject) => {
 
@@ -245,42 +226,41 @@ export class HttpHelper {
         this.httpHandlerList.get(apiNo ?? params.url).destroy()
         this.httpHandlerList.remove(apiNo ?? params.url)
         SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-        SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength, this.httpHandlerList.length)
+        SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
+          this.httpHandlerList.length)
       }
       let httpRequest = http.createHttp();
 
       if (this.concurrentList.getIndexOf(apiNo ?? params.url) === -1) {
         this.httpHandlerList.set(apiNo ?? params.url, httpRequest)
         SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-        SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength, this.httpHandlerList.length)
+        SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
+          this.httpHandlerList.length)
       }
 
-      const header = {
-        ...params.headers
+      if (showLog) {
+        LogHelper.debug(`GET:${apiNo}\n`, HttpHelperX.getUrl(params.url, params.query) + '\n',
+          JSON.stringify(params.headers))
       }
-      console.log('=====>', 'GET:', this.getUrl(params.url, params.query))
-      console.log('=====>', 'header:', JSON.stringify(header))
 
-      httpRequest.request(this.getUrl(params.url, params.query), {
+      httpRequest.request(HttpHelperX.getUrl(params.url, params.query), {
         method: http.RequestMethod.GET,
         connectTimeout: 20000,
         readTimeout: 20000,
-        header: header,
+        header: params.headers,
         // extraData: params.data
       })
         .then((data: http.HttpResponse) => {
-          console.info(`=====>Result:${data.result as string}(${apiNo})`);
-          // console.info('=====>' + 'code:' + data.responseCode);
-          // console.info('=====>' + 'type:' + JSON.stringify(data.resultType));
-          // console.info('=====>' + 'header:' + JSON.stringify(data.header));
-          // console.info('=====>' + 'cookies:' + data.cookies); // 自API version 8开始支持cookie
-          // console.info('=====>' + 'header.content-Type:' + JSON.stringify(data.header));
-          // console.info('=====>' + 'header.Status-Line:' + JSON.stringify(data.header));
+          if (showLog) {
+            LogHelper.debug(`${apiNo}:\n${data.result as string}`)
+            LogHelper.print(data)
+          }
 
           if (this.httpHandlerList.hasKey(apiNo ?? params.url)) {
             this.httpHandlerList.remove(apiNo ?? params.url)
             SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-            SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength, this.httpHandlerList.length)
+            SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
+              this.httpHandlerList.length)
           }
           if (data.responseCode === 200) {
             resolve((typeof data.result === 'string' ? JSON.parse(data.result) : data.result) as T)
@@ -291,11 +271,12 @@ export class HttpHelper {
             reject(err)
           }
         }).catch((err: Error) => {
-        console.info('=====>' + 'Error:' + JSON.stringify({ err: err, url: params.url, }));
+        LogHelper.error(JSON.stringify({ err: err, url: params.url, }))
         if (this.httpHandlerList.hasKey(apiNo ?? params.url)) {
           this.httpHandlerList.remove(apiNo ?? params.url)
           SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-          SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength, this.httpHandlerList.length)
+          SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
+            this.httpHandlerList.length)
         }
         if (err.message === 'Failed writing received data to disk/application') {
           reject('cancel')
@@ -306,42 +287,4 @@ export class HttpHelper {
     });
 
   }
-
-  private getUrl(url: string, query?: Record<string, string> | Object) {
-    let u = url
-    if (query) {
-      let q = query
-      if (typeof query === 'object') {
-        q = ToolsHelperForTS.classToRecord(query)
-      }
-      u = `${u}${u.indexOf('?') < 0 ? '?' : u.endsWith('$') ? '' : '&'}`
-      Object.entries(q).forEach((row) => {
-        if (row[1]) {
-          u = `${u}${row[0]}=${row[1] as string}&`
-        }
-      });
-      u = u.slice(0, u.length - 1)
-    }
-    return u
-  }
-
-  private getContent(data?: Record<string, string> | Object) {
-    if (!data) {
-      return undefined
-    }
-    let u = ''
-    let q = data
-    if (typeof data === 'object') {
-      q = ToolsHelperForTS.classToRecord(data)
-    }
-    Object.entries(q).forEach((row) => {
-      if (row[1]) {
-        u = `${u}${row[0]}=${row[1] as string}&`
-      }
-    });
-    u = u.slice(0, u.length - 1)
-    return u
-  }
-
-
 }

+ 73 - 0
src/main/ets/http/HttpHelperX.ts

@@ -0,0 +1,73 @@
+import { ToolsHelperForTS } from '../utils/ToolsHelperForTS';
+
+export interface HttpParamsGet {
+  url: string
+  query?: Record<string, string> | Object
+  headers?: Record<string, string | null | undefined>
+}
+
+export interface HttpParamsPost {
+  url: string
+  data?: string | Object | ArrayBuffer
+  query?: Record<string, string> | Object
+  headers?: Record<string, string | null | undefined>
+}
+
+export interface HttpParamsForm {
+  url: string
+  data?: Record<string, string> | Object | undefined
+  query?: Record<string, string> | Object
+  headers?: Record<string, string | null | undefined>
+}
+
+export class HttpHelperX {
+  /**
+   *
+   * @param ct Content-Type
+   * @param headers
+   * @returns
+   */
+  static getHeaders(ct: string, headers?: Record<string, string>) {
+    return {
+      "Content-Type": ct,
+      // "Accept": "application/json",
+      ...headers
+    }
+  }
+
+  static getUrl(url: string, query?: Record<string, string> | Object) {
+    let u = url
+    if (query) {
+      let q = query
+      if (typeof query === 'object') {
+        q = ToolsHelperForTS.classToRecord(query)
+      }
+      u = `${u}${u.indexOf('?') < 0 ? '?' : u.endsWith('$') ? '' : '&'}`
+      Object.entries(q).forEach((row) => {
+        if (row[1]) {
+          u = `${u}${row[0]}=${row[1] as string}&`
+        }
+      });
+      u = u.slice(0, u.length - 1)
+    }
+    return u
+  }
+
+  static getContent(data?: Record<string, string> | Object) {
+    if (!data) {
+      return undefined
+    }
+    let u = ''
+    let q = data
+    if (typeof data === 'object') {
+      q = ToolsHelperForTS.classToRecord(data)
+    }
+    Object.entries(q).forEach((row) => {
+      if (row[1]) {
+        u = `${u}${row[0]}=${row[1] as string}&`
+      }
+    });
+    u = u.slice(0, u.length - 1)
+    return u
+  }
+}

+ 71 - 0
src/main/ets/utils/Base64Helper.ets

@@ -0,0 +1,71 @@
+import { util } from '@kit.ArkTS';
+
+export class Base64Helper {
+  private constructor() {
+  }
+
+
+  /**
+   * 编码,通过输入参数编码后输出Uint8Array对象。
+   * @param array
+   * @returns
+   */
+  static encode(array: Uint8Array): Promise<Uint8Array> {
+    let base64 = new util.Base64Helper();
+    return base64.encode(array);
+  }
+
+  /**
+   * 编码,通过输入参数编码后输出Uint8Array对象。
+   * @param array
+   * @returns
+   */
+  static encodeSync(array: Uint8Array): Uint8Array {
+    let base64 = new util.Base64Helper();
+    let result = base64.encodeSync(array);
+    return result;
+  }
+
+  /**
+   * 编码,通过输入参数编码后输出对应文本。
+   * @param array
+   * @returns
+   */
+  static encodeToStr(array: Uint8Array, options?: util.Type): Promise<string> {
+    let base64 = new util.Base64Helper();
+    return base64.encodeToString(array, options);
+  }
+
+  /**
+   * 编码,通过输入参数编码后输出对应文本。
+   * @param array
+   * @returns
+   */
+  static encodeToStrSync(array: Uint8Array, options?: util.Type): string {
+    let base64 = new util.Base64Helper();
+    let result = base64.encodeToStringSync(array, options);
+    return result;
+  }
+
+
+  /**
+   * 解码,通过输入参数解码后输出对应Uint8Array对象。
+   * @param array
+   * @returns
+   */
+  static decode(array: Uint8Array | string, options?: util.Type): Promise<Uint8Array> {
+    let base64 = new util.Base64Helper();
+    return base64.decode(array, options);
+  }
+
+  /**
+   * 解码,通过输入参数解码后输出对应Uint8Array对象。
+   * @param array
+   * @returns
+   */
+  static decodeSync(array: Uint8Array | string, options?: util.Type): Uint8Array {
+    let base64 = new util.Base64Helper();
+    let result = base64.decodeSync(array, options);
+    return result;
+  }
+}

+ 105 - 0
src/main/ets/utils/CharHelper.ets

@@ -0,0 +1,105 @@
+import { i18n } from '@kit.LocalizationKit';
+
+export class CharHelper {
+  private constructor() {
+  }
+
+
+  /**
+   * 判断字符串char是否是数字
+   * @param char
+   * @returns
+   */
+  static isDigit(char: string): boolean {
+    return i18n.Unicode.isDigit(char);
+  }
+
+  /**
+   * 判断字符串char是否是字母
+   * @param char
+   * @returns
+   */
+  static isLetter(char: string): boolean {
+    return i18n.Unicode.isLetter(char);
+  }
+
+  /**
+   * 判断字符串char是否是小写字母
+   * @param char
+   * @returns
+   */
+  static isLowerCase(char: string): boolean {
+    return i18n.Unicode.isLowerCase(char);
+  }
+
+  /**
+   * 判断字符串char是否是大写字母
+   * @param char
+   * @returns
+   */
+  static isUpperCase(char: string): boolean {
+    return i18n.Unicode.isUpperCase(char);
+  }
+
+  /**
+   * 判断字符串char是否是空格符
+   * @param char
+   * @returns
+   */
+  static isSpaceChar(char: string): boolean {
+    return i18n.Unicode.isSpaceChar(char);
+  }
+
+  /**
+   * 判断字符串char是否是空白符
+   * @param char
+   * @returns
+   */
+  static isWhitespace(char: string): boolean {
+    return i18n.Unicode.isWhitespace(char);
+  }
+
+  /**
+   * 判断字符串char是否是从右到左语言的字符
+   * @param char
+   * @returns
+   */
+  static isRTL(char: string): boolean {
+    return i18n.Unicode.isRTL(char);
+  }
+
+  /**
+   * 判断字符串char是否是表意文字
+   * @param char
+   * @returns
+   */
+  static isIdeograph(char: string): boolean {
+    return i18n.Unicode.isIdeograph(char);
+  }
+
+
+  /**
+   * 判断是否空白符 空白符包括空格、制表符、全角空格和不间断空格
+   * @param c
+   * @returns
+   */
+  static isBlankChar(c: number): boolean {
+    return CharHelper.isWhitespace(c.toString())
+      || CharHelper.isSpaceChar(c.toString())
+      || c == 0xFEFF || c == 0x202A || c == 0x0000;
+  }
+
+
+  /**
+   * 判断字符是否位于ASCII范围内(其中0-31是控制字符,32-127表示从A到Z的字母字符)
+   * @param char 字符
+   * @returns
+   */
+  static isAscii(char: string): boolean {
+    if (char.length == 1) { //确保输入的是单个字符
+      return char.charCodeAt(0) < 128;
+    } else {
+      return false;
+    }
+  }
+}

+ 170 - 0
src/main/ets/utils/CrashHelper.ets

@@ -0,0 +1,170 @@
+import { appRecovery, common, errorManager, Want } from '@kit.AbilityKit';
+import { TimeHelper } from './TimeHelper';
+import { FileHelper } from './FileHelper';
+import { PreferencesHelper } from './PreferencesHelper';
+import { BusinessError } from '@kit.BasicServicesKit';
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { StrHelper } from './StrHelper';
+import { ToolsHelper } from './ToolsHelper';
+import { PickerHelper } from './PickerHelper';
+import { LogHelper } from './LogHelper';
+
+export class CrashHelper {
+  private constructor() {
+  }
+
+  private static readonly idKey: string = 'crash_observer_id_key'
+  private static observerId: number = -100;
+  private static ErrorFilePath: string = ''; //错误日志文件路径
+
+
+  /**
+   * 注册错误观测器。注册后可以捕获到应用产生的js crash,应用崩溃时进程不会退出。将异常信息写入本地文件。
+   */
+  static onError() {
+    try {
+      PreferencesHelper.get(CrashHelper.idKey).then(res => {
+        if (res !== undefined && (res as number) !== -100) {
+          CrashHelper.offError(); //如果存在,就先注销错误观测器。
+        }
+        CrashHelper.observerId = errorManager.on('error', {
+          onUnhandledException(errMsg) {
+            let errStr = `${TimeHelper.getTime()} - 异常信息:\n${errMsg}\n\n\n`;
+            LogHelper.error(errMsg)
+            CrashHelper.ErrorFilePath = FileHelper.getFilesDirPath("ErrorLog", "errorLog.txt")
+            FileHelper.writeEasy(CrashHelper.ErrorFilePath, errStr);
+          },
+          onException(errObject) {
+            // let errStr = `${DateUtil.getTodayStr()} - 异常信息2:\n${JSON.stringify(errObject)}\n\n\n`;
+          }
+        });
+        PreferencesHelper.put(CrashHelper.idKey, CrashHelper.observerId)
+      })
+    } catch (err) {
+      let error = err as BusinessError;
+      hilog.error(0x0000, '=====>', `CrashHelper-onError-异常 ~ code: ${error.code} -·- message: ${error.message}`);
+    }
+  }
+
+
+  /**
+   * 注销错误观测器。
+   */
+  static offError() {
+    try {
+      if (CrashHelper.observerId === -100) {
+        return
+      }
+      errorManager.off('error', CrashHelper.observerId, (err: BusinessError) => {
+        if (err) {
+          hilog.error(0x0000, '=====>', "CrashHelper:" + JSON.stringify(err))
+          return;
+        }
+        PreferencesHelper.put(CrashHelper.idKey, -100)
+      })
+    } catch (err) {
+      let error = err as BusinessError;
+      hilog.error(0x0000, '=====>', `CrashHelper-offError-异常 ~ code: ${error.code} -·- message: ${error.message}`);
+    }
+  }
+
+
+  /**
+   * 导出错误日志
+   */
+  static onExportErrorLog() {
+    CrashHelper.ErrorFilePath = FileHelper.getFilesDirPath("ErrorLog", "errorLog.txt")
+    if (StrHelper.isNotEmpty(CrashHelper.ErrorFilePath)) {
+      PickerHelper.saveDocument(['errorLog.txt']).then((documentSaveResult: Array<string>) => {
+        if (documentSaveResult && documentSaveResult.length > 0) {
+          let saveUri = documentSaveResult[0];
+          let file = FileHelper.openSync(saveUri);
+          FileHelper.copyFile(CrashHelper.ErrorFilePath, file.fd).then(() => {
+            FileHelper.close(file.fd);
+          });
+        }
+      })
+    } else {
+      ToolsHelper.showMessage('暂无日志文件')
+    }
+  }
+
+  /**
+   * 清空已有日志
+   */
+  static clearErrorLog() {
+    FileHelper.unlinkSync(CrashHelper.ErrorFilePath)
+    CrashHelper.ErrorFilePath = FileHelper.getFilesDirPath("ErrorLog", "errorLog.txt")
+  }
+
+
+  /**
+   * 读取错误日志文件
+   */
+  static async readErrorText(): Promise<string> {
+    CrashHelper.ErrorFilePath = FileHelper.getFilesDirPath("ErrorLog", "errorLog.txt")
+    if (StrHelper.isNotEmpty(CrashHelper.ErrorFilePath)) {
+      if (FileHelper.accessSync(CrashHelper.ErrorFilePath)) {
+        return await FileHelper.readText(CrashHelper.ErrorFilePath);
+      }
+    }
+    return '';
+  }
+
+
+  /**
+   * 启用应用恢复功能,参数按顺序填入。该接口调用后,应用从启动器启动时第一个Ability支持恢复。
+   * @param restart RestartFlag 应用重启标志。
+   *    ALWAYS_RESTART  0  总是重启应用。
+   *    RESTART_WHEN_JS_CRASH  0x0001  发生JS_CRASH时重启应用。
+   *    RESTART_WHEN_APP_FREEZE  0x0002  发生APP_FREEZE时重启应用。
+   *    NO_RESTART  0xFFFF  总是不重启应用。
+   * @param saveOccasion SaveOccasionFlag 保存条件标志
+   *    SAVE_WHEN_ERROR  0x0001  当发生应用故障时保存。
+   *    SAVE_WHEN_BACKGROUND  0x0002  当应用切入后台时保存。
+   * @param saveMode SaveModeFlag  状态保存标志
+   *    SAVE_WITH_FILE  0x0001  每次状态保存都会写入到本地文件缓存。
+   *    SAVE_WITH_SHARED_MEMORY  0x0002  状态先保存在内存中,应用故障退出时写入到本地文件缓存。
+   */
+  static enableAppRecovery(restart: appRecovery.RestartFlag = appRecovery.RestartFlag.ALWAYS_RESTART,
+    saveOccasion: appRecovery.SaveOccasionFlag = appRecovery.SaveOccasionFlag.SAVE_WHEN_ERROR,
+    saveMode: appRecovery.SaveModeFlag.SAVE_WITH_FILE = appRecovery.SaveModeFlag.SAVE_WITH_FILE) {
+    appRecovery.enableAppRecovery(restart, saveOccasion, saveMode);
+  }
+
+
+  /**
+   * 重启APP,并拉起应用启动时第一个Ability,可以配合errorManager相关接口使用。
+   * 如果该Ability存在已经保存的状态,这些状态数据会在Ability的OnCreate生命周期回调的want参数中作为wantParam属性传入。
+   * API10时将启动由setRestartWant指定的Ability。如果没有指定则按以下规则启动:
+   *   如果当前应用前台的Ability支持恢复,则重新拉起该Ability。
+   *   如果存在多个支持恢复的Ability处于前台,则只拉起最后一个。
+   *   如果没有Ability处于前台,则不拉起。
+   */
+  static restartApp() {
+    appRecovery.restartApp()
+  }
+
+
+  /**
+   * 设置下次恢复主动拉起场景下的Ability。该Ability必须为当前包下的UIAbility。
+   * @param want 通过设置Want中"bundleName"和"abilityName"字段来指定恢复重启的Ability。
+   */
+  static setRestartWant(want: Want) {
+    appRecovery.setRestartWant(want);
+  }
+
+
+  /**
+   * 保存当前App状态 或 主动保存Ability的状态,这个状态将在下次恢复启动时使用。可以配合errorManager相关接口使用
+   * @param context UIAbilityContext  需要保存状态的UIAbility所对应的context。
+   * @returns
+   */
+  static saveAppState(context?: common.UIAbilityContext): boolean {
+    if (context) {
+      return appRecovery.saveAppState(context) //主动保存Ability的状态
+    } else {
+      return appRecovery.saveAppState() //保存当前App状态
+    }
+  }
+}

+ 812 - 0
src/main/ets/utils/FileHelper.ets

@@ -0,0 +1,812 @@
+import fileUri from '@ohos.file.fileuri';
+import fs, { ListFileOptions, ReadOptions, ReadTextOptions, WriteOptions } from '@ohos.file.fs';
+import { BusinessError } from '@kit.BasicServicesKit';
+import { StrHelper } from './StrHelper';
+import { hilog } from '@kit.PerformanceAnalysisKit';
+
+export class FileHelper {
+  private constructor() {
+  }
+
+  static readonly separator: string = '/';
+
+  /**
+   * 获取文件目录下的文件夹路径或文件路径。
+   * @param dirPath 文件路径;支持完整路径和相对路径(download/wps/doc);dirPath传空字符串表示根目录
+   * @param fileName 文件名(test.text);fileName传空字符串表示文件夹路径
+   * @param blHap true:HAP级别文件路径、 false:App级别文件路径
+   * @returns
+   */
+  static getFilesDirPath(dirPath: string = "", fileName: string = "", blHap: boolean = true): string {
+    let filePath = blHap ? getContext().filesDir : getContext().getApplicationContext().filesDir; //根目录
+    if (StrHelper.isNotEmpty(dirPath)) {
+      if (StrHelper.startsWith(dirPath, filePath)) { //路径中包含根目录,是完整路径。
+        filePath = dirPath;
+      } else { //路径中不包含根目录,拼接成完整路径。
+        filePath = filePath + FileHelper.separator + dirPath;
+      }
+      if (!FileHelper.accessSync(filePath)) {
+        FileHelper.mkdirSync(filePath) //如果文件夹不存在就创建
+      }
+    }
+    if (StrHelper.isNotEmpty(fileName)) {
+      filePath = filePath + FileHelper.separator + fileName;
+    }
+    return filePath;
+  }
+
+
+  /**
+   * 获取缓存目录下的文件夹路径或文件路径。
+   * @param dirPath 文件路径;支持完整路径和相对路径(download/wps/doc);dirPath传空字符串表示根目录
+   * @param fileName 文件名(test.text);fileName传空字符串表示文件夹路径
+   * @param blHap true:HAP级别文件路径、 false:App级别文件路径
+   * @returns
+   */
+  static getCacheDirPath(dirPath: string = "", fileName: string = "", blHap: boolean = true): string {
+    let filePath = blHap ? getContext().cacheDir : getContext().getApplicationContext().cacheDir; //根目录
+    if (StrHelper.isNotEmpty(dirPath)) {
+      if (FileHelper.hasDirPath(dirPath)) { //路径中包含根目录,是完整路径。
+        filePath = dirPath;
+      } else { //路径中不包含根目录,拼接成完整路径。
+        filePath = filePath + FileHelper.separator + dirPath;
+      }
+      if (!FileHelper.accessSync(filePath)) {
+        FileHelper.mkdirSync(filePath) //如果文件夹不存在就创建
+      }
+    }
+    if (StrHelper.isNotEmpty(fileName)) {
+      filePath = filePath + FileHelper.separator + fileName;
+    }
+    return filePath;
+  }
+
+
+  /**
+   * 获取临时目录下的文件夹路径或文件路径。
+   * @param dirPath 文件路径;支持完整路径和相对路径(download/wps/doc);dirPath传空字符串表示根目录
+   * @param fileName 文件名(test.text);fileName传空字符串表示文件夹路径
+   * @param blHap true:HAP级别文件路径、 false:App级别文件路径
+   * @returns
+   */
+  static getTempDirPath(dirPath: string = "", fileName: string = "", blHap: boolean = true): string {
+    let filePath = blHap ? getContext().tempDir : getContext().getApplicationContext().tempDir; //根目录
+    if (StrHelper.isNotEmpty(dirPath)) {
+      if (FileHelper.hasDirPath(dirPath)) { //路径中包含根目录,是完整路径。
+        filePath = dirPath;
+      } else { //路径中不包含根目录,拼接成完整路径。
+        filePath = filePath + FileHelper.separator + dirPath;
+      }
+      if (!FileHelper.accessSync(filePath)) {
+        FileHelper.mkdirSync(filePath) //如果文件夹不存在就创建
+      }
+    }
+    if (StrHelper.isNotEmpty(fileName)) {
+      filePath = filePath + FileHelper.separator + fileName;
+    }
+    return filePath;
+  }
+
+
+  /**
+   * 判断是否是完整路径
+   * @param path 文件路径
+   */
+  static hasDirPath(path: string): boolean {
+    return StrHelper.startsWith(path, "/data/storage/el1/") || StrHelper.startsWith(path, "/data/storage/el2/");
+  }
+
+
+  /**
+   * 通过URI或路径,获取FileUri
+   * @param uriOrPath URI或路径
+   * @returns
+   */
+  static getFileUri(uriOrPath: string): fileUri.FileUri {
+    return new fileUri.FileUri(uriOrPath);
+  }
+
+  /**
+   * 通过URI或路径,获取文件名。
+   * @param uriOrPath URI或路径
+   * @returns
+   */
+  static getFileName(uriOrPath: string): string {
+    return FileHelper.getFileUri(uriOrPath).name;
+  }
+
+  /**
+   * 通过URI或路径,获取文件路径
+   * @param uriOrPath URI或路径
+   * @returns
+   */
+  static getFilePath(uriOrPath: string): string {
+    return FileHelper.getFileUri(uriOrPath).path;
+  }
+
+
+  /**
+   * 通过URI或路径,获取对应文件父目录的URI。
+   * @param uriOrPath URI或路径
+   */
+  static getParentUri(uriOrPath: string): string {
+    return FileHelper.getFileUri(uriOrPath).getFullDirectoryUri();
+  }
+
+  /**
+   * 通过URI或路径,获取对应文件父目录的路径名。
+   * @param uriOrPath URI或路径
+   */
+  static getParentPath(uriOrPath: string): string {
+    let parentUri = FileHelper.getParentUri(uriOrPath);
+    return FileHelper.getFilePath(parentUri)
+  }
+
+  /**
+   * 以同步方法获取文件URI。
+   * @param path 应用沙箱路径
+   * @returns
+   */
+  static getUriFromPath(path: string): string {
+    return fileUri.getUriFromPath(path);
+  }
+
+
+  /**
+   * 根据文件名获取文件后缀
+   * @param fileName 例如: test.txt  test.doc
+   * @returns
+   */
+  static getFileExtention(fileName: string) {
+    if (StrHelper.isNotEmpty(fileName) && fileName.includes(".")) {
+      return fileName.substring(fileName.lastIndexOf(".") + 1);
+    }
+    return '';
+  }
+
+
+  /**
+   * 获取指定文件夹下所有文件的大小或指定文件大小。
+   * @param path 文件夹路径 或 文件路径
+   */
+  static getFileDirSize(path: string): number {
+    if (FileHelper.accessSync(path)) { //path存在
+      if (FileHelper.isDirectory(path)) { //文件夹
+        let count: number = 0;
+        FileHelper.listFileSync(path, { recursion: true }).forEach((filePath) => {
+          count = count + FileHelper.lstatSync(path + filePath).size
+        })
+        return count;
+      } else { //文件
+        return FileHelper.lstatSync(path).size
+      }
+    }
+    return 0;
+  }
+
+
+  /**
+   * 判断文件是否是普通文件。
+   * @param file string|number 文件应用沙箱路径path或已打开的文件描述符fd。
+   * @returns
+   */
+  static isFile(file: string | number): boolean {
+    return fs.statSync(file).isFile();
+  }
+
+  /**
+   * 判断文件是否是目录。
+   * @param file string|number 文件应用沙箱路径path或已打开的文件描述符fd。
+   * @returns
+   */
+  static isDirectory(file: string | number): boolean {
+    return fs.statSync(file).isDirectory();
+  }
+
+
+  /**
+   * 重命名文件或文件夹,使用Promise异步回调。
+   * @param oldPath string 文件的应用沙箱原路径。
+   * @param newPath string 文件的应用沙箱新路径。
+   * @returns
+   */
+  static rename(oldPath: string, newPath: string): Promise<void> {
+    return fs.rename(oldPath, newPath);
+  }
+
+  /**
+   * 重命名文件或文件夹,以同步方法。
+   * @param oldPath string 文件的应用沙箱原路径。
+   * @param newPath string 文件的应用沙箱新路径。
+   * @returns
+   */
+  static renameSync(oldPath: string, newPath: string) {
+    fs.renameSync(oldPath, newPath);
+  }
+
+
+  /**
+   * 创建目录,当recursion指定为true,可多层级创建目录,使用Promise异步回调。
+   * @param path 目录的应用沙箱路径。
+   * @param recursion 是否多层级创建目录。recursion指定为true时,可多层级创建目录。recursion指定为false时,仅可创建单层目录。
+   * @returns
+   */
+  static mkdir(path: string, recursion: boolean = true): Promise<void> {
+    if (recursion) {
+      return fs.mkdir(path, recursion);
+    } else {
+      return fs.mkdir(path);
+    }
+  }
+
+  /**
+   * 创建目录以同步方法,当recursion指定为true,可多层级创建目录。
+   * @param path 目录的应用沙箱路径。
+   * @param recursion 是否多层级创建目录。recursion指定为true时,可多层级创建目录。recursion指定为false时,仅可创建单层目录。
+   */
+  static mkdirSync(path: string, recursion: boolean = true) {
+    if (recursion) {
+      fs.mkdirSync(path, recursion);
+    } else {
+      fs.mkdirSync(path);
+    }
+  }
+
+
+  /**
+   * 删除整个目录,使用Promise异步回调。
+   * @param path 目录的应用沙箱路径。
+   * @returns
+   */
+  static rmdir(path: string): Promise<void> {
+    return fs.rmdir(path);
+  }
+
+  /**
+   * 删除整个目录,以同步方法。
+   * @param path 目录的应用沙箱路径。
+   */
+  static rmdirSync(path: string) {
+    return fs.rmdirSync(path);
+  }
+
+
+  /**
+   * 删除单个文件,使用Promise异步回调。
+   * @param path 文件的应用沙箱路径。
+   * @returns
+   */
+  static unlink(path: string): Promise<void> {
+    return fs.unlink(path);
+  }
+
+  /**
+   * 删除单个文件,以同步方法。
+   * @param path 文件的应用沙箱路径。
+   * @returns
+   */
+  static unlinkSync(path: string) {
+    fs.unlinkSync(path);
+  }
+
+
+  /**
+   * 检查文件是否存在,使用Promise异步回调。
+   * @param path 文件应用沙箱路径。
+   * @returns
+   */
+  static access(path: string): Promise<boolean> {
+    return fs.access(path);
+  }
+
+  /**
+   * 检查文件是否存在,以同步方法。
+   * @param path 文件应用沙箱路径。
+   * @returns
+   */
+  static accessSync(path: string): boolean {
+    return fs.accessSync(path);
+  }
+
+
+  /**
+   * 打开文件,支持使用URI打开文件。使用Promise异步回调。
+   * @param path string 文件的应用沙箱路径或URI。
+   * @param mode number 打开文件的选项,必须指定如下选项中的一个,默认以只读方式打开。
+   * @returns
+   */
+  static open(path: string, mode: number = fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE): Promise<fs.File> {
+    return fs.open(path, mode);
+  }
+
+  /**
+   * 打开文件,支持使用URI打开文件。以同步方法。
+   * @param path string 文件的应用沙箱路径或URI。
+   * @param mode number 打开文件的选项,必须指定如下选项中的一个,默认以只读方式打开。
+   * @returns
+   */
+  static openSync(path: string, mode: number = fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE): fs.File {
+    return fs.openSync(path, mode);
+  }
+
+
+  /**
+   * 从文件读取数据,使用Promise异步回调。
+   * @param fd number 已打开的文件描述符。
+   * @param buffer ArrayBuffer 用于保存读取到的文件数据的缓冲区。
+   * @param options 支持如下选项:
+   *                 offset,number类型,表示期望读取文件的位置。可选,默认从当前位置开始读。
+   *                 length,number类型,表示期望读取数据的长度。可选,默认缓冲区长度。
+   * @returns
+   */
+  static read(fd: number, buffer: ArrayBuffer, options?: ReadOptions): Promise<number> {
+    return fs.read(fd, buffer, options)
+  }
+
+  /**
+   * 从文件读取数据,以同步方法。
+   * @param fd number 已打开的文件描述符。
+   * @param buffer ArrayBuffer 用于保存读取到的文件数据的缓冲区。
+   * @param options 支持如下选项:
+   *                 offset,number类型,表示期望读取文件的位置。可选,默认从当前位置开始读。
+   *                 length,number类型,表示期望读取数据的长度。可选,默认缓冲区长度。
+   * @returns
+   */
+  static readSync(fd: number, buffer: ArrayBuffer, options?: ReadOptions): number {
+    return fs.readSync(fd, buffer, options)
+  }
+
+  /**
+   * 基于文本方式读取文件(即直接读取文件的文本内容),使用Promise异步回调。
+   * @param filePath 文件的应用沙箱路径。
+   * @param options 支持如下选项:
+   *                 offset,number类型,表示期望读取文件的位置。可选,默认从当前位置开始读取。
+   *                 length,number类型,表示期望读取数据的长度。可选,默认文件长度。
+   *                 encoding,string类型,当数据是 string 类型时有效,表示数据的编码方式,默认 'utf-8',仅支持 'utf-8'。
+   * @returns
+   */
+  static readText(filePath: string, options?: ReadTextOptions): Promise<string> {
+    return fs.readText(filePath, options);
+  }
+
+  /**
+   * 基于文本方式读取文件(即直接读取文件的文本内容),以同步方法。
+   * @param filePath 文件的应用沙箱路径。
+   * @param options 支持如下选项:
+   *                 offset,number类型,表示期望读取文件的位置。可选,默认从当前位置开始读取。
+   *                 length,number类型,表示期望读取数据的长度。可选,默认文件长度。
+   *                 encoding,string类型,当数据是 string 类型时有效,表示数据的编码方式,默认 'utf-8',仅支持 'utf-8'。
+   * @returns
+   */
+  static readTextSync(filePath: string, options?: ReadTextOptions): string {
+    return fs.readTextSync(filePath, options);
+  }
+
+
+  /**
+   * 将数据写入文件,使用Promise异步回调。
+   * @param fd number 已打开的文件描述符。
+   * @param buffer ArrayBuffer|string 待写入文件的数据,可来自缓冲区或字符串。
+   * @param options 支持如下选项:
+   *                 offset,number类型,表示期望写入文件的位置。可选,默认从当前位置开始写。
+   *                 length,number类型,表示期望写入数据的长度。可选,默认缓冲区长度。
+   *                 encoding,string类型,当数据是string类型时有效,表示数据的编码方式,默认 'utf-8'。当前仅支持 'utf-8'。
+   * @returns
+   */
+  static write(fd: number, buffer: ArrayBuffer | string, options?: WriteOptions): Promise<number> {
+    return fs.write(fd, buffer, options)
+  }
+
+  /**
+   * 将数据写入文件,以同步方法。
+   * @param fd number 已打开的文件描述符。
+   * @param buffer ArrayBuffer|string 待写入文件的数据,可来自缓冲区或字符串。
+   * @param options 支持如下选项:
+   *                 offset,number类型,表示期望写入文件的位置。可选,默认从当前位置开始写。
+   *                 length,number类型,表示期望写入数据的长度。可选,默认缓冲区长度。
+   *                 encoding,string类型,当数据是string类型时有效,表示数据的编码方式,默认 'utf-8'。当前仅支持 'utf-8'。
+   * @returns
+   */
+  static writeSync(fd: number, buffer: ArrayBuffer | string, options?: WriteOptions): number {
+    return fs.writeSync(fd, buffer, options)
+  }
+
+  /**
+   * 将数据写入文件,并关闭文件。
+   * @param path string 文件的应用沙箱路径或URI。
+   * @param buffer ArrayBuffer|string 待写入文件的数据,可来自缓冲区或字符串。
+   * @param append 是否追加,true-追加,false-不追加(直接覆盖)
+   * @returns
+   */
+  static async writeEasy(path: string, buffer: ArrayBuffer | string, append: boolean = true): Promise<number> {
+    try {
+      let file = FileHelper.openSync(path, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
+      let offset = append ? FileHelper.statSync(file.fd).size : 0
+      let options: WriteOptions = { offset: offset, encoding: 'utf-8' };
+      return await FileHelper.write(file.fd, buffer, options).finally(() => {
+        FileHelper.close(file.fd); //关闭文件
+      });
+    } catch (err) {
+      let error = err as BusinessError;
+      hilog.error(0x0000, '=====>', `FileHelper-writeEasy-异常 ~ code: ${error.code} -·- message: ${error.message}`)
+      return -1;
+    }
+  }
+
+
+  /**
+   * 关闭文件,使用Promise异步回调。
+   * @param file 已打开的File对象或已打开的文件描述符fd。
+   * @returns
+   */
+  static close(file: fs.File | number): Promise<void> {
+    return fs.close(file);
+  }
+
+  /**
+   * 关闭文件,以同步方法。
+   * @param file 已打开的File对象或已打开的文件描述符fd。
+   */
+  static closeSync(file: fs.File | number) {
+    fs.closeSync(file);
+  }
+
+
+  /**
+   * 列出文件夹下所有文件名,支持递归列出所有文件名(包含子目录下),支持文件过滤,使用Promise异步回调。
+   * @param path string 文件夹的应用沙箱路径。
+   * @param options 文件过滤选项。默认不进行过滤。
+   *                  recursion boolean 是否递归子目录下文件名,默认为false。
+   *                  listNum number 列出文件名数量。当设置0时,列出所有文件,默认为0。
+   *                  filter Filter 文件过滤选项。当前仅支持后缀名匹配、文件名模糊查询、文件大小过滤、最近修改时间过滤。
+   * @returns
+   */
+  static listFile(path: string, options?: ListFileOptions): Promise<string[]> {
+    return fs.listFile(path, options);
+  }
+
+  /**
+   * 列出文件夹下所有文件名,支持递归列出所有文件名(包含子目录下),支持文件过滤,以同步方法。
+   * @param path string 文件夹的应用沙箱路径。
+   * @param options 文件过滤选项。默认不进行过滤。
+   *                  recursion boolean 是否递归子目录下文件名,默认为false。
+   *                  listNum number 列出文件名数量。当设置0时,列出所有文件,默认为0。
+   *                  filter Filter 文件过滤选项。当前仅支持后缀名匹配、文件名模糊查询、文件大小过滤、最近修改时间过滤。
+   * @returns
+   */
+  static listFileSync(path: string, options?: ListFileOptions): string[] {
+    return fs.listFileSync(path, options);
+  }
+
+
+  /**
+   * 获取文件详细属性信息,使用Promise异步回调。
+   * @param file string|number 文件应用沙箱路径path或已打开的文件描述符fd。
+   */
+  static stat(file: string | number): Promise<fs.Stat> {
+    return fs.stat(file);
+  }
+
+  /**
+   * 获取文件详细属性信息,以同步方法。
+   * @param file string|number 文件应用沙箱路径path或已打开的文件描述符fd。
+   * @returns
+   */
+  static statSync(file: string | number): fs.Stat {
+    return fs.statSync(file);
+  }
+
+
+  /**
+   * 拷贝文件或者目录,支持拷贝进度监听,使用Promise异步返回。
+   * @param srcUri 待复制文件或目录的uri。
+   * @param destUri 目标文件或目录的uri。
+   * @param options options中提供拷贝进度回调:
+   *           ProgressListener 拷贝进度监听。
+   * @returns
+   */
+  static copy(srcUri: string, destUri: string, options?: fs.CopyOptions): Promise<void> {
+    return fs.copy(srcUri, destUri, options);
+  }
+
+  /**
+   * 复制文件,使用Promise异步回调。
+   * @param src string|number 待复制文件的路径或待复制文件的文件描述符。
+   * @param dest string|number 目标文件路径或目标文件的文件描述符。
+   * @param mode number 提供覆盖文件的选项,当前仅支持0,且默认为0。0:完全覆盖目标文件。
+   * @returns
+   */
+  static copyFile(src: string | number, dest: string | number, mode: number = 0): Promise<void> {
+    return fs.copyFile(src, dest, mode);
+  }
+
+  /**
+   * 以同步方法复制文件。
+   * @param src string|number 待复制文件的路径或待复制文件的文件描述符。
+   * @param dest string|number 目标文件路径或目标文件的文件描述符。
+   * @param mode number 提供覆盖文件的选项,当前仅支持0,且默认为0。0:完全覆盖目标文件。
+   */
+  static copyFileSync(src: string | number, dest: string | number, mode: number = 0) {
+    fs.copyFileSync(src, dest, mode);
+  }
+
+  /**
+   * 复制源文件夹至目标路径下,只能复制沙箱里的文件夹,使用Promise异步返回。
+   * @param src 源文件夹的应用沙箱路径。
+   * @param dest 目标文件夹的应用沙箱路径。
+   * @param mode 复制模式:
+   *    mode为0,文件级别抛异常。目标文件夹下存在与源文件夹名冲突的文件夹,若冲突文件夹下存在同名文件,则抛出异常。源文件夹下未冲突的文件全部移动至目标文件夹下,目标文件夹下未冲突文件将继续保留,且冲突文件信息将在抛出异常的data属性中以Array<ConflictFiles>形式提供。
+   *    mode为1,文件级别强制覆盖。目标文件夹下存在与源文件夹名冲突的文件夹,若冲突文件夹下存在同名文件,则强制覆盖冲突文件夹下所有同名文件,未冲突文件将继续保留。
+   * @returns
+   */
+  static copyDir(src: string, dest: string, mode: number = 1): Promise<void> {
+    return fs.copyDir(src, dest, mode);
+  }
+
+  /**
+   * 以同步方法复制源文件夹至目标路径下,只能复制沙箱里的文件夹。
+   * @param src 源文件夹的应用沙箱路径。
+   * @param dest 目标文件夹的应用沙箱路径。
+   * @param mode 复制模式:
+   *    mode为0,文件级别抛异常。目标文件夹下存在与源文件夹名冲突的文件夹,若冲突文件夹下存在同名文件,则抛出异常。源文件夹下未冲突的文件全部移动至目标文件夹下,目标文件夹下未冲突文件将继续保留,且冲突文件信息将在抛出异常的data属性中以Array<ConflictFiles>形式提供。
+   *    mode为1,文件级别强制覆盖。目标文件夹下存在与源文件夹名冲突的文件夹,若冲突文件夹下存在同名文件,则强制覆盖冲突文件夹下所有同名文件,未冲突文件将继续保留。
+   * @returns
+   */
+  static copyDirSync(src: string, dest: string, mode: number = 1) {
+    fs.copyDirSync(src, dest, mode);
+  }
+
+
+  /**
+   * 移动文件,使用Promise异步回调。
+   * @param src string 源文件的应用沙箱路径。
+   * @param dest string 目的文件的应用沙箱路径。
+   * @param mode number 移动模式。若mode为0,移动位置存在同名文件时,强制移动覆盖。若mode为1,移动位置存在同名文件时,抛出异常。默认为0。
+   * @returns
+   */
+  static moveFile(src: string, dest: string, mode: number = 0): Promise<void> {
+    return fs.moveFile(src, dest, mode)
+  }
+
+  /**
+   * 移动文件,以同步方法。
+   * @param src string 源文件的应用沙箱路径。
+   * @param dest string 目的文件的应用沙箱路径。
+   * @param mode number 移动模式。若mode为0,移动位置存在同名文件时,强制移动覆盖。若mode为1,移动位置存在同名文件时,抛出异常。默认为0。
+   * @returns
+   */
+  static moveFileSync(src: string, dest: string, mode: number = 0) {
+    fs.moveFileSync(src, dest, mode)
+  }
+
+  /**
+   * 移动源文件夹至目标路径下,使用Promise异步返回。
+   * @param src 源文件夹的应用沙箱路径
+   * @param dest 目标文件夹的应用沙箱路径
+   * @param mode 移动模式:
+   *   mode为0,文件夹级别抛异常。若目标文件夹下存在与源文件夹名冲突的非空文件夹,则抛出异常。
+   *   mode为1,文件级别抛异常。目标文件夹下存在与源文件夹名冲突的文件夹,若冲突文件夹下存在同名文件,则抛出异常。源文件夹下未冲突的文件全部移动至目标文件夹下,目标文件夹下未冲突文件将继续保留,且冲突文件信息将在抛出异常的data属性中以Array<ConflictFiles>形式提供。
+   *   mode为2,文件级别强制覆盖。目标文件夹下存在与源文件夹名冲突的文件夹,若冲突文件夹下存在同名文件,则强制覆盖冲突文件夹下所有同名文件,未冲突文件将继续保留。
+   *   mode为3,文件夹级别强制覆盖。移动源文件夹至目标文件夹下,目标文件夹下移动的文件夹内容与源文件夹完全一致。若目标文件夹下存在与源文件夹名冲突的文件夹,该文件夹下所有原始文件将不会保留。
+   * @returns
+   */
+  static moveDir(src: string, dest: string, mode: number = 3): Promise<void> {
+    return fs.moveDir(src, dest, mode);
+  }
+
+  /**
+   * 以同步方法移动源文件夹至目标路径下。
+   * @param src 源文件夹的应用沙箱路径
+   * @param dest 目标文件夹的应用沙箱路径
+   * @param mode 移动模式:
+   *   mode为0,文件夹级别抛异常。若目标文件夹下存在与源文件夹名冲突的非空文件夹,则抛出异常。
+   *   mode为1,文件级别抛异常。目标文件夹下存在与源文件夹名冲突的文件夹,若冲突文件夹下存在同名文件,则抛出异常。源文件夹下未冲突的文件全部移动至目标文件夹下,目标文件夹下未冲突文件将继续保留,且冲突文件信息将在抛出异常的data属性中以Array<ConflictFiles>形式提供。
+   *   mode为2,文件级别强制覆盖。目标文件夹下存在与源文件夹名冲突的文件夹,若冲突文件夹下存在同名文件,则强制覆盖冲突文件夹下所有同名文件,未冲突文件将继续保留。
+   *   mode为3,文件夹级别强制覆盖。移动源文件夹至目标文件夹下,目标文件夹下移动的文件夹内容与源文件夹完全一致。若目标文件夹下存在与源文件夹名冲突的文件夹,该文件夹下所有原始文件将不会保留。
+   * @returns
+   */
+  static moveDirSync(src: string, dest: string, mode: number = 3) {
+    return fs.moveDirSync(src, dest, mode);
+  }
+
+
+  /**
+   * 截断文件,使用Promise异步回调。
+   * @param file string|number 文件的应用沙箱路径或已打开的文件描述符fd。
+   * @param len number 文件截断后的长度,以字节为单位。默认为0。
+   * @returns
+   */
+  static truncate(file: string | number, len: number = 0): Promise<void> {
+    return fs.truncate(file, len)
+  }
+
+  /**
+   * 截断文件,以同步方法。
+   * @param file string|number 文件的应用沙箱路径或已打开的文件描述符fd。
+   * @param len number 文件截断后的长度,以字节为单位。默认为0。
+   * @returns
+   */
+  static truncateSync(file: string | number, len: number = 0) {
+    fs.truncateSync(file, len)
+  }
+
+
+  /**
+   * 获取链接文件信息,使用Promise异步回调。
+   * @param path string 文件的应用沙箱路径。
+   * @returns
+   */
+  static lstat(path: string): Promise<fs.Stat> {
+    return fs.lstat(path);
+  }
+
+  /**
+   * 获取链接文件信息,以同步方法。
+   * @param path string 文件的应用沙箱路径。
+   * @returns
+   */
+  static lstatSync(path: string): fs.Stat {
+    return fs.lstatSync(path);
+  }
+
+
+  /**
+   * 同步文件数据,使用Promise异步回调。
+   * @param fd number 已打开的文件描述符。
+   * @returns
+   */
+  static fsync(fd: number): Promise<void> {
+    return fs.fsync(fd);
+  }
+
+  /**
+   * 同步文件数据,以同步方法。
+   * @param fd number 已打开的文件描述符。
+   */
+  static fsyncSync(fd: number) {
+    fs.fsyncSync(fd);
+  }
+
+
+  /**
+   * 实现文件内容数据同步,使用Promise异步回调。
+   * @param fd number 已打开的文件描述符。
+   * @returns
+   */
+  static fdatasync(fd: number): Promise<void> {
+    return fs.fdatasync(fd);
+  }
+
+  /**
+   * 实现文件内容数据同步,以同步方法。
+   * @param fd number 已打开的文件描述符。
+   */
+  static fdatasyncSync(fd: number) {
+    fs.fdatasyncSync(fd);
+  }
+
+
+  /**
+   * 基于文件路径打开文件流,使用Promise异步回调。
+   * @param path string 文件的应用沙箱路径。
+   * @param mode string 文件打开类型
+   *                    r:打开只读文件,该文件必须存在。
+   *                    r+:打开可读写的文件,该文件必须存在。
+   *                    w:打开只写文件,若文件存在则文件长度清0,即该文件内容会消失。若文件不存在则建立该文件。
+   *                    w+:打开可读写文件,若文件存在则文件长度清0,即该文件内容会消失。若文件不存在则建立该文件。
+   *                    a:以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。
+   *                    a+:以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。
+   * @returns
+   */
+  static createStream(path: string, mode: string = 'r'): Promise<fs.Stream> {
+    return fs.createStream(path, mode)
+  }
+
+  /**
+   * 基于文件路径打开文件流,以同步方法。
+   * @param path string 文件的应用沙箱路径。
+   * @param mode string 文件打开类型
+   *                    r:打开只读文件,该文件必须存在。
+   *                    r+:打开可读写的文件,该文件必须存在。
+   *                    w:打开只写文件,若文件存在则文件长度清0,即该文件内容会消失。若文件不存在则建立该文件。
+   *                    w+:打开可读写文件,若文件存在则文件长度清0,即该文件内容会消失。若文件不存在则建立该文件。
+   *                    a:以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。
+   *                    a+:以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。
+   * @returns
+   */
+  static createStreamSync(path: string, mode: string = 'r'): fs.Stream {
+    return fs.createStreamSync(path, mode);
+  }
+
+  /**
+   * 基于文件描述符打开文件流,使用Promise异步回调。
+   * @param fd number 已打开的文件描述符。
+   * @param mode string 文件打开类型
+   *                    r:打开只读文件,该文件必须存在。
+   *                    r+:打开可读写的文件,该文件必须存在。
+   *                    w:打开只写文件,若文件存在则文件长度清0,即该文件内容会消失。若文件不存在则建立该文件。
+   *                    w+:打开可读写文件,若文件存在则文件长度清0,即该文件内容会消失。若文件不存在则建立该文件。
+   *                    a:以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。
+   *                    a+:以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。
+   * @returns
+   */
+  static fdopenStream(fd: number, mode: string = 'r'): Promise<fs.Stream> {
+    return fs.fdopenStream(fd, mode)
+  }
+
+  /**
+   * 基于文件描述符打开文件流,以同步方法。
+   * @param fd number 已打开的文件描述符。
+   * @param mode string 文件打开类型
+   *                    r:打开只读文件,该文件必须存在。
+   *                    r+:打开可读写的文件,该文件必须存在。
+   *                    w:打开只写文件,若文件存在则文件长度清0,即该文件内容会消失。若文件不存在则建立该文件。
+   *                    w+:打开可读写文件,若文件存在则文件长度清0,即该文件内容会消失。若文件不存在则建立该文件。
+   *                    a:以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。
+   *                    a+:以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。
+   * @returns
+   */
+  static fdopenStreamSync(fd: number, mode: string = 'r'): fs.Stream {
+    return fs.fdopenStreamSync(fd, mode)
+  }
+
+
+  /**
+   * 创建临时目录,使用Promise异步回调。
+   * @param prefix string 用随机产生的字符串替换以“XXXXXX”结尾目录路径。
+   * @returns
+   */
+  static mkdtemp(prefix: string): Promise<string> {
+    return fs.mkdtemp(prefix);
+  }
+
+  /**
+   * 创建临时目录,以同步的方法。
+   * @param prefix string 用随机产生的字符串替换以“XXXXXX”结尾目录路径。
+   * @returns
+   */
+  static mkdtempSync(prefix: string): string {
+    return fs.mkdtempSync(prefix);
+  }
+
+
+  /**
+   * 将文件描述符转化为File。
+   * @param fd 文件描述符。
+   * @returns
+   */
+  static dup(fd: number): fs.File {
+    return fs.dup(fd);
+  }
+
+
+  /**
+   * 修改文件最近访问时间属性。
+   * path 文件的应用沙箱路径。
+   * mtime 待更新的时间戳。自1970年1月1日起至目标时间的毫秒数。仅支持修改文件最近访问时间属性。
+   * @returns
+   */
+  static utimes(path: string, mtime: number): void {
+    fs.utimes(path, mtime);
+  }
+
+
+  /**
+   * 格式化文件大小
+   * @param fileSize
+   * @returns
+   */
+  static getFormatFileSize(fileSize: number): string {
+    if (fileSize < 1024) {
+      return fileSize + "B";
+    } else if (fileSize < 1024 * 1024) {
+      return (fileSize / 1024).toFixed(1) + "KB";
+    } else if (fileSize < 1024 * 1024 * 1024) {
+      return (fileSize / (1024 * 1024)).toFixed(1) + "MB";
+    } else if (fileSize < 1024 * 1024 * 1024 * 1024) {
+      return (fileSize / (1024 * 1024 * 1024)).toFixed(1) + "GB";
+    } else {
+      return (fileSize / (1024 * 1024 * 1024 * 1024)).toFixed(1) + "TB";
+    }
+  }
+}

+ 134 - 0
src/main/ets/utils/LogHelper.ets

@@ -0,0 +1,134 @@
+import hilog from '@ohos.hilog'
+import { BusinessError } from '@kit.BasicServicesKit'
+
+const LOGGER_DOMAIN: number = 0x0000
+const LOGGER_TAG: string = '=====》'
+
+export class LogHelper {
+  private static domain: number = LOGGER_DOMAIN
+  private static tag: string = LOGGER_TAG //日志Tag
+  private static format: string = '%{public}s'
+  private static showLog: boolean = true //是否显示打印日志
+
+
+  /**
+   * 初始化日志参数(该方法建议在Ability里调用)
+   * @param domain
+   * @param tag
+   * @param showLog
+   */
+  static init(domain: number = LOGGER_DOMAIN, tag: string = LOGGER_TAG, showLog: boolean = true) {
+    LogHelper.domain = domain
+    LogHelper.tag = tag
+    LogHelper.showLog = showLog
+  }
+
+  /**
+   * 设置日志对应的领域标识,范围是0x0~0xFFFF。(该方法建议在Ability里调用)
+   * @param domain
+   */
+  static setDomain(domain: number = LOGGER_DOMAIN) {
+    LogHelper.domain = domain
+  }
+
+  /**
+   * 设置日志标识(该方法建议在Ability里调用)
+   * @param tag
+   */
+  static setTag(tag: string = LOGGER_TAG) {
+    LogHelper.tag = tag
+  }
+
+  /**
+   * 是否打印日志(该方法建议在Ability里调用)
+   * @param showLog
+   */
+  static setShowLog(showLog: boolean = true) {
+    LogHelper.showLog = showLog
+  }
+
+  /**
+   * 打印DEBUG级别日志
+   * @param args
+   */
+  static debug(...args: string[]): void {
+    if (LogHelper.showLog) {
+      hilog.debug(LogHelper.domain, LogHelper.tag, ' ')
+      hilog.debug(LogHelper.domain, LogHelper.tag, LogHelper.format.repeat(args.length), args)
+    }
+  }
+
+  /**
+   * 打印INFO级别日志
+   * @param args
+   */
+  static info(...args: string[]): void {
+    if (LogHelper.showLog) {
+      hilog.info(LogHelper.domain, LogHelper.tag, ' ')
+      hilog.info(LogHelper.domain, LogHelper.tag, LogHelper.format.repeat(args.length), args)
+    }
+  }
+
+  /**
+   * 打印WARN级别日志
+   * @param args
+   */
+  static warn(...args: string[]): void {
+    if (LogHelper.showLog) {
+      hilog.warn(LogHelper.domain, LogHelper.tag, ' ')
+      hilog.warn(LogHelper.domain, LogHelper.tag, LogHelper.format.repeat(args.length), args)
+    }
+  }
+
+  /**
+   * 打印ERROR级别日志
+   * @param args
+   */
+  static error(...args: string[]): void {
+    if (LogHelper.showLog) {
+      hilog.error(LogHelper.domain, LogHelper.tag, ' ')
+      hilog.error(LogHelper.domain, LogHelper.tag, LogHelper.format.repeat(args.length), args)
+    }
+  }
+
+  /**
+   * 打印FATAL级别日志
+   * @param args
+   */
+  static fatal(...args: string[]): void {
+    if (LogHelper.showLog) {
+      hilog.fatal(LogHelper.domain, LogHelper.tag, ' ')
+      hilog.fatal(LogHelper.domain, LogHelper.tag, LogHelper.format.repeat(args.length), args)
+    }
+  }
+
+
+  /**
+   * 打印JSON对象和JSON字符串
+   * @param obj
+   */
+  static print(obj: object | string) {
+    try {
+      console.debug('')
+      if (typeof obj === 'object') {
+        let str = JSON.stringify(obj, null, 2)
+        let arr: string[] = str.split('\n')
+        for (let index = 0; index < arr.length; index++) {
+          console.debug(arr[index])
+        }
+      } else {
+        obj = JSON.parse(obj)
+        let str = JSON.stringify(obj, null, 2)
+        let arr = str.split('\n')
+        for (let index = 0; index < arr.length; index++) {
+          console.debug(arr[index])
+        }
+      }
+    } catch (err) {
+      let error = err as BusinessError; //异常了,说明不是JSON字符串
+      LogHelper.error(`LogHelper-print-异常 ~ code: ${error.code} -·- message: ${error.message}`);
+    }
+  }
+}
+
+// export default new LogHelper() //单例(在ES6模块中,当你使用 import 导入一个模块时,实际上是在导入该模块的值的一个引用。这意味着在另一个模块中修改该值会影响原始模块中的值。)

+ 219 - 0
src/main/ets/utils/PickerHelper.ets

@@ -0,0 +1,219 @@
+import picker from '@ohos.file.picker';
+import { camera, cameraPicker } from '@kit.CameraKit';
+import { common } from '@kit.AbilityKit';
+import { BusinessError } from '@kit.BasicServicesKit';
+import { hilog } from '@kit.PerformanceAnalysisKit';
+
+
+const DOCUMENT_DEFAULT_SELECT_NUMBER: number = 9; //数量
+
+export class PickerHelper {
+  private constructor() {
+  }
+
+
+  /**
+   * 调用系统相机,拍照、录视频
+   * @param options
+   * @returns
+   */
+  static async camera(options?: CameraOptions): Promise<string> {
+    try {
+      if (!options) {
+        options = new CameraOptions();
+      }
+      if (!options.mediaTypes || options.mediaTypes.length == 0) {
+        options.mediaTypes = [cameraPicker.PickerMediaType.PHOTO, cameraPicker.PickerMediaType.VIDEO];
+      }
+      let pickerProfile: cameraPicker.PickerProfile = {
+        cameraPosition: options.cameraPosition ? options.cameraPosition : camera.CameraPosition.CAMERA_POSITION_BACK,
+        videoDuration: options.videoDuration,
+        saveUri: options.saveUri
+      };
+
+      let context = getContext() as common.Context;
+      let pickerResult: cameraPicker.PickerResult = await cameraPicker.pick(context,
+        options.mediaTypes, pickerProfile);
+      if (pickerResult && pickerResult.resultUri) {
+        return pickerResult.resultUri;
+      }
+    } catch (err) {
+      let error = err as BusinessError;
+      hilog.error(0x0000, '=====>', `PickerHelper-camera-异常 ~ code: ${error.code} -·- message: ${error.message}`);
+    }
+    return "";
+  }
+
+
+  /**
+   * 通过选择模式拉起PhotoViewPicker界面,用户可以选择一个或多个图片/视频。
+   * @param options
+   * @returns
+   */
+  static async selectPhoto(options?: picker.PhotoSelectOptions): Promise<Array<string>> {
+    try {
+      if (!options) {
+        options = new picker.PhotoSelectOptions();
+      }
+      if (!options.MIMEType) { //可选择的媒体文件类型,若无此参数,则默认为图片和视频类型。
+        options.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
+      }
+      if (!options.maxSelectNumber) { //选择媒体文件数量的最大值,默认9
+        options.maxSelectNumber = DOCUMENT_DEFAULT_SELECT_NUMBER;
+      }
+      let photoPicker = new picker.PhotoViewPicker();
+      let photoSelectResult: picker.PhotoSelectResult = await photoPicker.select(options);
+      if (photoSelectResult && photoSelectResult.photoUris && photoSelectResult.photoUris.length > 0) {
+        return photoSelectResult.photoUris;
+      } else {
+        return [];
+      }
+    } catch (err) {
+      let error = err as BusinessError;
+      hilog.error(0x0000, '=====>',
+        `PickerHelper-selectPhoto-异常 ~ code: ${error.code} -·- message: ${error.message}`);
+      return [];
+    }
+  }
+
+
+  /**
+   * 通过保存模式拉起photoPicker进行保存图片或视频资源的文件名,若无此参数,则默认需要用户自行输入
+   * @param newFileNames
+   */
+  static async savePhoto(newFileNames?: Array<string>): Promise<Array<string>> {
+    try {
+      let photoPicker = new picker.PhotoViewPicker();
+      if (newFileNames == undefined || newFileNames == null || newFileNames.length == 0) {
+        let photoSaveResult = await photoPicker.save();
+        if (photoSaveResult && photoSaveResult.length > 0) {
+          return photoSaveResult;
+        } else {
+          return [];
+        }
+      } else {
+        let PhotoSaveOptions = new picker.PhotoSaveOptions();
+        PhotoSaveOptions.newFileNames = newFileNames;
+        let photoSaveResult = await photoPicker.save(PhotoSaveOptions);
+        if (photoSaveResult && photoSaveResult.length > 0) {
+          return photoSaveResult;
+        } else {
+          return [];
+        }
+      }
+    } catch (err) {
+      let error = err as BusinessError;
+      hilog.error(0x0000, '=====>', `PickerHelper-savePhoto-异常 ~ code: ${error.code} -·- message: ${error.message}`);
+      return [];
+    }
+  }
+
+
+  /**
+   * 通过选择模式拉起documentPicker界面,用户可以选择一个或多个文件。
+   * @param options
+   * @returns
+   */
+  static async selectDocument(options?: picker.DocumentSelectOptions): Promise<Array<string>> {
+    try {
+      if (!options) {
+        options = new picker.DocumentSelectOptions();
+      }
+      if (!options.maxSelectNumber) { //选择媒体文件数量的最大值,默认9
+        options.maxSelectNumber = DOCUMENT_DEFAULT_SELECT_NUMBER;
+      }
+      if (!options.selectMode) { //支持选择的资源类型,默认文件
+        options.selectMode = picker.DocumentSelectMode.FILE;
+      }
+      let documentPicker = new picker.DocumentViewPicker();
+      return await documentPicker.select(options);
+    } catch (err) {
+      let error = err as BusinessError;
+      hilog.error(0x0000, '=====>',
+        `PickerHelper-selectDocument-异常 ~ code: ${error.code} -·- message: ${error.message}`);
+      return [];
+    }
+  }
+
+
+  /**
+   * 通过保存模式拉起documentPicker界面,用户可以保存一个或多个文件。
+   * @param options
+   * @returns
+   */
+  static async saveDocument(newFileNames?: Array<string>): Promise<Array<string>> {
+    try {
+      let documentPicker = new picker.DocumentViewPicker();
+      if (newFileNames == undefined || newFileNames == null || newFileNames.length == 0) {
+        return await documentPicker.save();
+      } else {
+        let documentSaveOptions = new picker.DocumentSaveOptions();
+        documentSaveOptions.newFileNames = newFileNames;
+        return await documentPicker.save(documentSaveOptions);
+      }
+    } catch (err) {
+      let error = err as BusinessError;
+      hilog.error(0x0000, '=====>',
+        `PickerHelper-saveDocument-异常 ~ code: ${error.code} -·- message: ${error.message}`);
+      return [];
+    }
+  }
+
+
+  /**
+   * 通过选择模式拉起audioPicker界面(目前拉起的是documentPicker,audioPicker在规划中),用户可以选择一个或多个音频文件。
+   * @returns
+   */
+  static async selectAudio(options?: picker.AudioSelectOptions): Promise<Array<string>> {
+    try {
+      if (!options) {
+        options = new picker.AudioSelectOptions();
+      }
+      if (!options.maxSelectNumber) { //选择媒体文件数量的最大值,默认9
+        options.maxSelectNumber = DOCUMENT_DEFAULT_SELECT_NUMBER;
+      }
+      let audioPicker = new picker.AudioViewPicker();
+      return await audioPicker.select(options);
+    } catch (err) {
+      let error = err as BusinessError;
+      hilog.error(0x0000, '=====>',
+        `PickerHelper-selectAudio-异常 ~ code: ${error.code} -·- message: ${error.message}`);
+      return [];
+    }
+  }
+
+
+  /**
+   * 通过保存模式拉起audioPicker界面(目前拉起的是documentPicker,audioPicker在规划中),用户可以保存一个或多个音频文件。
+   * @param newFileNames
+   * @returns
+   */
+  static async saveAudio(newFileNames?: Array<string>): Promise<Array<string>> {
+    try {
+      let audioPicker = new picker.AudioViewPicker();
+      if (newFileNames == undefined || newFileNames == null || newFileNames.length == 0) {
+        return await audioPicker.save();
+      } else {
+        let AudioSaveOptions = new picker.AudioSaveOptions();
+        AudioSaveOptions.newFileNames = newFileNames;
+        return await audioPicker.save(AudioSaveOptions);
+      }
+    } catch (err) {
+      let error = err as BusinessError;
+      hilog.error(0x0000, '=====>', `PickerHelper-saveAudio-异常 ~ code: ${error.code} -·- message: ${error.message}`);
+      return [];
+    }
+  }
+}
+
+
+/**
+ * 相机参数类
+ */
+export class CameraOptions {
+  mediaTypes: Array<cameraPicker.PickerMediaType> =
+    [cameraPicker.PickerMediaType.PHOTO, cameraPicker.PickerMediaType.VIDEO]; //媒体类型。
+  cameraPosition: camera.CameraPosition = camera.CameraPosition.CAMERA_POSITION_BACK; //相机的位置。
+  saveUri?: string; //保存配置信息的uri。
+  videoDuration?: number; //录制的最大时长。
+}

+ 347 - 0
src/main/ets/utils/StrHelper.ets

@@ -0,0 +1,347 @@
+import { CharHelper } from './CharHelper';
+import { buffer, util } from '@kit.ArkTS';
+import { Base64Helper } from './Base64Helper';
+
+export class StrHelper {
+  private constructor() {
+  }
+
+
+  /**
+   * 判断字符串是否为空(undefined、null)
+   * @param str 被检测的字符串
+   * @returns 是否为空
+   */
+  static isNull(str: string | undefined | null): boolean {
+    return str == undefined || str == null;
+  }
+
+  /**
+   * 判断字符串是否为非空。true为非空空,否则false
+   * @param str
+   * @returns
+   */
+  static isNotNull(str: string | undefined | null): boolean {
+    return false == StrHelper.isNull(str);
+  }
+
+
+  /**
+   * 判断字符串是否为空(undefined、null、字符串长度为0)
+   * @param str 被检测的字符串
+   * @return 是否为空
+   */
+  static isEmpty(str: string | undefined | null): boolean {
+    return str == undefined || str == null || str.length == 0;
+  }
+
+  /**
+   * 判断字符串是否为非空。true为非空空,否则false
+   * @param str
+   * @returns
+   */
+  static isNotEmpty(str: string | undefined | null): boolean {
+    return false == StrHelper.isEmpty(str);
+  }
+
+
+  /**
+   * 判断字符串是否为空和空白符(空白符包括空格、制表符、全角空格和不间断空格)。true为空,否则false
+   * @param str
+   * @returns
+   */
+  static isBlank(str: string | undefined | null): boolean {
+    let length: number = 0;
+    if ((str == undefined) || (str == null) || ((length = str.length) == 0)) {
+      return true;
+    }
+    for (let i = 0; i < length; i++) {
+      if (false == CharHelper.isBlankChar(str.charCodeAt(i))) {
+        return false; //只要有一个非空字符即为非空字符串
+      }
+    }
+    return true;
+  }
+
+  /**
+   * 判断字符串是否为非空和空白符(空白符包括空格、制表符、全角空格和不间断空格)true为非空,否则false
+   * @param str
+   * @returns
+   */
+  static isNotBlank(str: string | undefined | null): boolean {
+    return false == StrHelper.isBlank(str);
+  }
+
+
+  /**
+   * 格式化字符串
+   * @param source
+   * @param defaultValue
+   * @returns
+   */
+  static toStr(source: string | null | undefined, defaultValue = ""): string {
+    if (source == null || source == undefined) {
+      return defaultValue;
+    }
+    return String(source);
+  }
+
+
+  /**
+   * 替换字符串中匹配的正则为给定的字符串
+   * @param str   待替换的字符串
+   * @param pattern  要匹配的内容正则或字符串
+   * @param replaceValue 替换的内容
+   * @returns
+   */
+  static replace(str: string, pattern: RegExp | string, replaceValue: string = ''): string {
+    return str.replace(pattern, replaceValue);
+  }
+
+  /**
+   * 替换字符串中所有匹配的正则为给定的字符串
+   * @param str  待替换的字符串
+   * @param pattern  要匹配的内容正则或字符串
+   * @param replaceValue 替换的内容
+   * @returns 返回替换后的字符串
+   */
+  static replaceAll(str: string, pattern: RegExp | string, replaceValue: string = ''): string {
+    return str.replaceAll(pattern, replaceValue);
+  }
+
+  /**
+   * 判断字符串是否以给定的字符串开头
+   * @param string 要检索的字符串
+   * @param target 要检索字符
+   * @param position 检索的位置
+   * @returns
+   */
+  static startsWith(string: string = '', target: string, position: number = 0): boolean {
+    return string.startsWith(target, position);
+  }
+
+
+  /**
+   * 判断字符串是否以给定的字符串结尾
+   * @param str 要检索的字符串
+   * @param target 要检索字符
+   * @param position 检索的位置
+   * @returns
+   */
+  static endsWith(str: string = '', target: string, position: number = str.length): boolean {
+    return str.endsWith(target, position);
+  }
+
+
+  /**
+   * 将字符串重复指定次数
+   * @param str  要重复的字符串
+   * @param n  重复的次数
+   * @returns
+   */
+  static repeat(str: string = '', n: number = 1): string {
+    return str.repeat(n);
+  }
+
+
+  /**
+   * 将整个字符串转换为小写
+   * @param str 要转换的字符串
+   * @returns 返回小写的字符串
+   */
+  static toLower(str: string = ''): string {
+    return str.toLowerCase();
+  }
+
+
+  /**
+   * 将整个字符串转换为大写
+   * @param str 要转换的字符串
+   * @returns 返回小写的字符串
+   */
+  static toUpper(str: string = ''): string {
+    return str.toUpperCase();
+  }
+
+
+  /**
+   * 将字符串首字母转换为大写,剩下为小写
+   * @param str 待转换的字符串
+   * @returns 转换后的
+   */
+  static capitalize(str: string = ''): string {
+    if (StrHelper.isNotEmpty(str)) {
+      const firstChar = str.charAt(0).toUpperCase();
+      const restChars = str.slice(1).toLowerCase();
+      return firstChar + restChars;
+    }
+    return '';
+  }
+
+
+  /**
+   * 判断两个传入的数值或者是字符串是否相等
+   * @param source
+   * @param target
+   * @returns
+   */
+  static equal(source: string | number, target: string | number): boolean {
+    return source === target;
+  }
+
+  /**
+   * 判断两个传入的数值或者是字符串是否不相等
+   * @param source
+   * @param target
+   * @returns
+   */
+  static notEqual(source: string | number, target: string | number): boolean {
+    return false == StrHelper.equal(source, target);
+  }
+
+
+  /**
+   * 字符串转Uint8Array
+   * @param src 字符串
+   * @returns Uint8Array
+   */
+  public static strToUint8Array(src: string, encoding: buffer.BufferEncoding = 'utf-8'): Uint8Array {
+    let textEncoder = new util.TextEncoder(encoding);
+    let result = textEncoder.encodeInto(src);
+    return result;
+  }
+
+  /**
+   * Uint8Array转字符串
+   * @param src Uint8Array
+   * @returns 字符串
+   */
+  static unit8ArrayToStr(src: Uint8Array, encoding: buffer.BufferEncoding = 'utf-8'): string {
+    let textDecoder = util.TextDecoder.create(encoding, { ignoreBOM: true })
+    let result = textDecoder.decodeWithStream(src, { stream: true });
+    return result;
+  }
+
+
+  /**
+   * 16进制字符串转换unit8Array
+   * @param hexStr
+   * @returns
+   */
+  static strToHex(hexStr: string): Uint8Array {
+    return new Uint8Array(buffer.from(hexStr, 'hex').buffer);
+  }
+
+  /**
+   * 16进制unit8Array转字符串
+   * @param arr
+   * @returns
+   */
+  static hexToStr(arr: Uint8Array): string {
+    return buffer.from(arr).toString('hex');
+  }
+
+
+  /**
+   * Bytes转字符串
+   * @param bytes
+   * @returns
+   */
+  public static bytesToStr(bytes: Uint8Array): string {
+    let str = ""
+    for (let i = 0; i < bytes.length; i++) {
+      str += String.fromCharCode(bytes[i]);
+
+    }
+    return str;
+  }
+
+  /**
+   * 字符串转Bytes
+   * @param str
+   * @returns
+   */
+  public static strToBytes(str: string): Uint8Array {
+    let bytes: number[] = new Array();
+    for (let i = 0; i < str.length; i++) {
+      bytes.push(str.charCodeAt(i))
+    }
+    return new Uint8Array(bytes);
+  }
+
+
+  /**
+   * 字符串转Base64字符串
+   * @param src 字符串
+   * @returns
+   */
+  static strToBase64(src: string): string {
+    let uint8Array = StrHelper.strToUint8Array(src);
+    let result = Base64Helper.encodeToStrSync(uint8Array);
+    return result;
+  }
+
+
+  /**
+   * Base64字符串转字符串
+   * @param base64Str Base64字符串
+   * @returns
+   */
+  static base64ToStr(base64Str: string): string {
+    let uint8Array = Base64Helper.decodeSync(base64Str);
+    let result = StrHelper.unit8ArrayToStr(uint8Array);
+    return result;
+  }
+
+
+  /**
+   * 字符串转ArrayBuffer
+   * @param str
+   * @returns
+   */
+  static strToBuffer(src: string, encoding: buffer.BufferEncoding = 'utf-8'): ArrayBuffer {
+    let buf = buffer.from(src, encoding);
+    return buf.buffer;
+  }
+
+  /**
+   * ArrayBuffer转字符串
+   * @param str
+   * @returns
+   */
+  static bufferToStr(src: ArrayBuffer, encoding: buffer.BufferEncoding = 'utf-8'): string {
+    let buf = buffer.from(src);
+    let result = buf.toString(encoding);
+    return result;
+  }
+
+
+  /**
+   * ArrayBuffer转Uint8Array
+   * @param str
+   * @returns
+   */
+  static bufferToUint8Array(src: ArrayBuffer): Uint8Array {
+    return new Uint8Array(src);
+  }
+
+  /**
+   * Uint8Array转ArrayBuffer
+   * @param str
+   * @returns
+   */
+  static unit8ArrayToBuffer(src: Uint8Array): ArrayBuffer {
+    // return buffer.from(src).buffer;
+    return src.buffer as ArrayBuffer;
+  }
+
+
+  /**
+   * 获取系统错误码对应的详细信息
+   * @param errno 错误码
+   * @returns
+   */
+  static getErrnoToString(errno: number): string {
+    return util.errnoToString(errno);
+  }
+}

+ 7 - 4
src/main/ets/utils/TimeHelper.ts

@@ -25,17 +25,20 @@ export class TimeHelper {
   /**
    * 获取当前时间,指定返回样式
    * @param formats 时间格式
-   * 'yyyy年MM月dd日'
+   * 'yyyy-MM-dd HH:mm:ss'
    */
+  static getTime(format: string = 'yyyy-MM-dd HH:mm:ss') {
+    return TimeHelper.formatDate(new Date(),format)
+  }
   static formatDate(date: Date, format: string) {
     const replacements: { [key: string]: string } = {
-      YYYY: date.getFullYear().toString(),
+      yyyy: date.getFullYear().toString(),
       MM: String(date.getMonth() + 1).padStart(2, "0"),
-      DD: String(date.getDate()).padStart(2, "0"),
+      dd: String(date.getDate()).padStart(2, "0"),
       HH: String(date.getHours()).padStart(2, "0"),
       mm: String(date.getMinutes()).padStart(2, "0"),
       ss: String(date.getSeconds()).padStart(2, "0")
     };
-    return format.replace(/YYYY|MM|DD|HH|mm|ss/g, matched => replacements[matched]);
+    return format.replace(/yyyy|MM|dd|HH|mm|ss/g, matched => replacements[matched]);
   }
 }

+ 3 - 2
src/main/ets/utils/ToolsHelper.ets

@@ -4,6 +4,7 @@ import { HashMap } from '@kit.ArkTS';
 import { DeviceInfo } from '../bean/DeviceInfo';
 import { common } from '@kit.AbilityKit';
 import { md5_hex } from '../util/md5';
+import { LogHelper } from '../../../../Index';
 
 export interface Btn {
   text?: string | Resource;
@@ -105,7 +106,7 @@ export class ToolsHelper {
    */
   static log(...args: ESObject[]) {
     const k = ToolsHelper.getStackKey()?.split('/')
-    console.log(`========>${k ? k[k.length-1].split('.')[0] : ''}::`, args, '\n')
+    LogHelper.info(`${k ? k[k.length-1].split('.')[0] : ''}::\n`, ...args)
   }
 
   /**
@@ -349,7 +350,7 @@ export class ToolsHelper {
   }
 
   private static getUniqueId(fun: Function): string {
-    ToolsHelper.log(ToolsHelper.getStackKey())
+    // ToolsHelper.log(ToolsHelper.getStackKey())
     if (!ToolsHelper.uniqueIdMap.has(fun)) {
       ToolsHelper.uniqueIdMap.set(fun, ToolsHelper.getUuid());
     }

+ 10 - 2
src/main/ets/utils/ToolsHelperForTS.ts

@@ -1,4 +1,4 @@
-import { HashMap } from "@kit.ArkTS";
+import { HashMap } from '@kit.ArkTS';
 
 /**
  * 常用方法,部分方法,在ets里面不能用
@@ -18,7 +18,7 @@ export class ToolsHelperForTS {
     return myMap;
   }
 
-  public static  classToRecord(obj: Object): Record<string, string> {
+  public static classToRecord(obj: Object): Record<string, string> {
     const record: Record<string, string> = {} as Record<string, string>;
     for (const key in obj) {
       if (obj.hasOwnProperty(key)) {
@@ -27,4 +27,12 @@ export class ToolsHelperForTS {
     }
     return record;
   }
+
+  static gets(headers?: Record<string, string>) {
+    return {
+      "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
+      // "Accept": "application/json",
+      ...headers
+    }
+  }
 }