cocosCreator 之localStorage本地存储和封装拓展

版本: 3.8.0

语言: TypeScript

环境: Mac


简介


在cocosCreator中,针对于本地存储主要使用localStorage接口,通过key-value的格式进行存储和读取数据。

主要接口有:

接口描述
setItem(key, value)保存指定索引的数据
getItem(key)获取指定索引的数据
removeItem(key)移除指定索引的数据
clear()清空所有数据

定义文件如下:

// cc.d.ts
export const sys: {
  // HTML5 标准中的 localStorage 的本地存储功能,在 Web 端等价于 window.localStorage
  localStorage: Storage;
}

// lib.dom.d.ts
interface Storage {
    // 返回数据项的数量
    readonly length: number;
    // 移除所有存储的数据
    clear(): void;
    // 根据键名获取数据,如果没有则null
    getItem(key: string): string | null;
    // 获取指定索引处的键名,如果没有则null
    key(index: number): string | null;
    // 根据键名移除指定数据
    removeItem(key: string): void;
    // 存储键名和数据, 注意可能会存在存储已满的情况,这样会抛出异常
    setItem(key: string, value: string): void;
    [name: string]: any;
}

在cocosCreator中,本地数据的存储是以sqlite数据库格式存储的。

我们以setItem简单看下引擎的封装相关:

  • C++相关,目录在: …/engine-native/cocos/storage/local-storage
// LocalStorage.cpp
void localStorageSetItem(const std::string &key, const std::string &value) {
  assert(_initialized);
  int ok = sqlite3_bind_text(_stmt_update, 1, key.c_str(), -1, SQLITE_TRANSIENT);
  ok |= sqlite3_bind_text(_stmt_update, 2, value.c_str(), -1, SQLITE_TRANSIENT);

  ok |= sqlite3_step(_stmt_update);

  ok |= sqlite3_reset(_stmt_update);

  if (ok != SQLITE_OK && ok != SQLITE_DONE)
    printf("Error in localStorage.setItem()\n");
}
  • Android平台相关, 目录在 …/libcocos/intermediates/javac/com/cocos/lib
// LocalStorage-android.cpp
void localStorageSetItem(const std::string &key, const std::string &value) {
  assert(gInitialized);
  JniHelper::callStaticVoidMethod(JCLS_LOCALSTORAGE, "setItem", key, value);
}

// CocosLocalStorage.class 
public static void setItem(String key, String value) {
  try {
    String sql = "replace into " + TABLE_NAME + "(key,value)values(?,?)";
    mDatabase.execSQL(sql, new Object[]{key, value});
  } catch (Exception var3) {
    var3.printStackTrace();
  }
}

简单的看下内部的实现,了解本地数据的存储在sqlite数据库中即可。


使用


脚本中使用本地存储,常用的接口是:

  • setItem(key: string, value: string): void; 存储数据

  • getItem(key: string): string | null 获取数据

  • removeItem(key: string): void; 移除数据

简单的示例:

const key = "Debug_Storage";
// 保存数据
sys.localStorage.setItem(key, "cocosCreator");
// 获取数据
let value = sys.localStorage.getItem(key);
console.log("----- 存储的数据是:", value);

注意:

  • setItem存储的数据是string类型,因此存储数据时,注意对数据类型转换
  • setItem的存储存在已满的情况,注意异常的发生
  • getItem获取数据为stringnull, 注意对返回数据的安全判定

因此,在项目中可增加对localStorage的封装管理,以支持:

  1. 支持不同基础数据类型的存储,包括但不限于string类型, 使用数据转换即可

  2. 支持数组、Map等复杂数据类型的存储, 使用 Json 转换

  3. 数据读取,支持默认数据的设置

Json转换的主要接口:

  • JSON.stringify 将数据转换为Json字符串
  • JSON.parse 用于将Json字符串解析为数据

主要实现逻辑如下:

import { _decorator, sys} from 'cc';
const { ccclass, property } = _decorator;

export class StorageManager {
  private static _instance: StorageManager = null;
  static get instance() {
    if (this._instance) {
      return this._instance;
    }
    this._instance = new StorageManager();
    return this._instance;
  }

  // 保存数据
  public setItem(key: string, value:any) {
    if (value === undefined || value === null) {
      console.log(`本地存储数据非法, key:${key}`);
      return;
    }
    let valueType = typeof(value);
    if (valueType === "number" && isNaN(value)) {
      console.log(`本地存储数据为NaN, key:${key}`);
      return;
    } 

    // 转换数据
    if (valueType === "number") {
      value = value.toString();
    } else if (valueType === "boolean") {
      // boolean类型转换为0或1
      value = value ? "1" : "0";
    } else if (valueType === "object") {
      // 数组或Map类型转换为JSON字符串
      value = JSON.stringify(value);
    }
    sys.localStorage.setItem(key, value);
  }

  // 读取数据
  public getItem(key: string, defaultValue: any = ""): any {
    let value = sys.localStorage.getItem(key);
    // 数据获取失败,就走默认设置
    if (value === null) {
      return defaultValue;
    }

    // 检测是否为JSON字符串
    const regex = /^\s*{[\s\S]*}\s*$/;
    if (regex.test(value)) {
      return JSON.parse(value);
    }
    return value;
  }
}

测试用例:

private debugStorage() {
  let storageManager = StorageManager.instance;

  // 检测数据合法性
  storageManager.setItem("Storage_Debug_1", null);
  storageManager.setItem("Storage_Debug_2", undefined);
  storageManager.setItem("Storage_Debug_3", NaN);

  // 存储
  storageManager.setItem("Storage_Int", 10);
  storageManager.setItem("Storage_Boolean", true);
  storageManager.setItem("Storage_Array1", [1,2,3]);
  storageManager.setItem("Storage_Array2", new Array(4,5,6));
  storageManager.setItem("Storage_Map", {name: "TypeScript", index:10});

  // 获取数据
  console.log("Storage_Int", storageManager.getItem("Storage_Int"));
  console.log("Storage_Boolean", storageManager.getItem("Storage_Boolean"));
  console.log("Storage_Array1", storageManager.getItem("Storage_Array1"));
  console.log("Storage_Array2", storageManager.getItem("Storage_Array2"));
  console.log("Storage_Map", storageManager.getItem("Storage_Map"));
}

请添加图片描述

至于 removeItem, key, clear等实现,直接调用localStorage的相关方法即可。


拓展1: 支持保存多份数据


在实际的项目开发中,频繁的功能测试可能需要我们保存多份本地存储数据。

可以通过key键 + 玩家的唯一标识符ID的方式,存储不同用户的数据,以实现保存多份。

StorageManager类的大概修改,可以这样:

// 初始化角色ID, 可用于项目获取用户数据成功后进行设置
private _roleId: string = "";
public setRoleId(id: string) {
	this._roleId = id;
}

// 增加新方法
private getNewKey(key: string) {
  let newKey = key;
  if (this._roleId.length <= 0) {
    newKey = `${key}_${this._roleId}`;
  }
  return newKey;
}

// 在setItem或getItem的接口中调用getNewKey即可

使用${key}_${this._roleId} 这种方式构建key,可避免重复性的key导致数据被覆盖。


拓展2: 数据安全

虽然cocosCreator采用的是sqlite数据库存储,但数据的存在是明文性的,这样不利于项目的安全。

因此项目有必要采用加密算法对明文内容进行加密,需要参考博客:cocosCreator 之 crypto-es数据加密

使用加密算法对本地存储数据可做如下处理:

  • key采取MD5加密
  • value 的存储数据进行AES加密,获取数据时进行AES解密

也就是在数据保存或获取前,对 keyvalue 进行下加密处理,最终实现代码:

import { _decorator, sys} from 'cc';
const { ccclass, property } = _decorator;
import CryptoES from "crypto-es";
import { EncryptUtil } from './EncryptUtil';

export class StorageManager {
  private static _instance: StorageManager = null;
  private _secretKey: string = "";
  private _roleId: string = "";

  static get instance() {
    if (this._instance) {
      return this._instance;
    }
    this._instance = new StorageManager();
    this._instance.init();
    return this._instance;
  }

  private init() {
    EncryptUtil.initCrypto("key", "vi");
  }

  // 设置角色ID
  public setRoleId(id: string) {
    this._roleId = id;
  }

  private getNewKey(key: string) {
    let newKey = key;
    if (this._roleId.length <= 0) {
      newKey = `${key}_${this._roleId}`;
    }
    return EncryptUtil.md5(newKey);
  }

  // 保存数据
  public setItem(key: string, value:any) {
    if (value === undefined || value === null) {
      console.log(`本地存储数据非法, key:${key}`);
      return;
    }
    let valueType = typeof(value);
    if (valueType === "number" && isNaN(value)) {
      console.log(`本地存储数据为NaN, key:${key}`);
      return;
    } 

    if (valueType === "number") {
      value = value.toString();
    } else if (valueType === "boolean") {
      value = value ? "1" : "0";
    } else if (valueType === "object") {
      value = JSON.stringify(value);
    }
    // 加密数据
    let newKey = this.getNewKey(key);
    let newValue = EncryptUtil.aesEncrypt(value);
    sys.localStorage.setItem(newKey, newValue);
  }

  // 读取数据
  public getItem(key: string, defaultValue: any = ""): any {
    let newKey = this.getNewKey(key);
    let value = sys.localStorage.getItem(newKey);
    // 数据获取失败,就走默认设置
    if (value === null) {
      return defaultValue;
    }
    // 解密数据
    let newValue = EncryptUtil.aesDecrypt(value);
    // 检测是否为JSON字符串
    const regex = /^\s*{[\s\S]*}\s*$/;
    if (regex.test(value)) {
      return JSON.parse(newValue);
    }

    return value;
  }
}

至此,所有内容讲述完毕,祝大家学习生活愉快!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鹤九日

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值