版本: 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获取数据为
string
或null
, 注意对返回数据的安全判定
因此,在项目中可增加对localStorage
的封装管理,以支持:
-
支持不同基础数据类型的存储,包括但不限于
string
类型, 使用数据转换即可 -
支持数组、Map等复杂数据类型的存储, 使用 Json 转换
-
数据读取,支持默认数据的设置
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解密
也就是在数据保存或获取前,对 key 和 value 进行下加密处理,最终实现代码:
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;
}
}
至此,所有内容讲述完毕,祝大家学习生活愉快!