跳到主要内容

跨端开发

@cloudbase/js-sdk 只支持常规 Web 应用(即浏览器环境)的开发,不兼容其他类 Web 平台,比如微信小程序、快应用、Cocos 等。虽然这些平台大多支持 JavaScript 运行环境,但在网络请求、本地存储、平台标识等特性上与浏览器环境有明显差异。针对这些差异特性,@cloudbase/js-sdk 提供一套完整的适配扩展方案,遵循此方案规范可开发对应平台的适配器,然后搭配 @cloudbase/js-sdk 和适配器实现平台的兼容性。

适配规范

开发适配器之前需要安装官方提供的接口声明模块@cloudbase/adapter-interface

# npm
npm i @cloudbase/adapter-interface
# yarn
yarn add @cloudbase/adapter-interface

适配器模块需要导出一个adapter对象:

const adapter = {
genAdapter,
isMatch,
// runtime标记平台唯一性
runtime: '平台名称'
};

export adapter;
export default adapter;

必须包含以下三个字段:

  • runtime: string,平台的名称,用于标记平台唯一性;
  • isMatch: Function,判断当前运行环境是否为平台,返回boolean值;
  • genAdapter: Function,创建adapter实体。

runtime

runtime用于标记平台的唯一性,建议尽量以平台的英文名称或简写命名,比如百度小程序baidu_miniapp、QQ 小程序qq_miniapp等等。

isMatch

isMatch函数用于判断当前运行环境是否与适配器匹配,通常是通过判断平台特有的一些全局变量、API 等。比如以下代码是判断运行环境是否为 Cocos 原生平台:

function isMatch(): boolean {
if (typeof cc === "undefined") {
return false;
}
if (typeof WebSocket === "undefined") {
return false;
}
if (typeof XMLHttpRequest === "undefined") {
return false;
}

if (!cc.game) {
return false;
}
if (typeof cc.game.on !== "function") {
return false;
}
if (!cc.game.EVENT_HIDE) {
return false;
}
if (!cc.game.EVENT_SHOW) {
return false;
}

if (!cc.sys) {
return false;
}
if (!cc.sys.isNative) {
return false;
}

return true;
}

genAdapter

genAdapter函数返回适配器的实体对象,结构如下:

interface SDKAdapterInterface {
// 全局根变量,浏览器环境为window
root: any;
// WebSocket类
wsClass: WebSocketContructor;
// request类
reqClass: SDKRequestConstructor;
// 无localstorage时persistence=local降级为none
localStorage?: StorageInterface;
// 无sessionStorage时persistence=session降级为none
sessionStorage?: StorageInterface;
// storage模式首选,优先级高于persistence
primaryStorage?: StorageType;
// Captcha验证码配置
captchaOptions?: {
// 打开网页并通过URL回调获取 CaptchaToken,针对不同的平台,该函数可以自定义实现
openURIWithCallback?: (url: string) => Promise<CaptchaToken>;
};
// 获取平台唯一应用标识的api
getAppSign?(): string;
}

示例

import {
AbstractSDKRequest,
IRequestOptions,
IUploadRequestOptions,
StorageInterface,
WebSocketInterface,
WebSocketContructor,
SDKAdapterInterface,
StorageType,
formatUrl,
} from "@cloudbase/adapter-interface";

// isMatch函数判断当前平台是否匹配
function isMatch(): boolean {
// ...
return true;
}

// Request类为平台特有的网络请求,必须实现post/upload/download三个public接口
export class Request extends AbstractSDKRequest {
// 实现post接口
public post(options: IRequestOptions) {
return new Promise((resolve) => {
// ...
resolve();
});
}
// 实现upload接口
public upload(options: IUploadRequestOptions) {
return new Promise((resolve) => {
// ...
resolve();
});
}
// 实现download接口
public download(options: IRequestOptions) {
return new Promise((resolve) => {
// ...
resolve();
});
}
}
// Storage为平台特有的本地存储,必须实现setItem/getItem/removeItem/clear四个接口
export const Storage: StorageInterface = {
setItem(key: string, value: any) {
// ...
},
getItem(key: string): any {
// ...
},
removeItem(key: string) {
// ...
},
clear() {
// ...
},
};
// WebSocket为平台特有的WebSocket,与HTML5标准规范一致
export class WebSocket {
constructor(url: string, options: object = {}) {
const socketTask: WebSocketInterface = {
set onopen(cb) {
// ...
},
set onmessage(cb) {
// ...
},
set onclose(cb) {
// ...
},
set onerror(cb) {
// ...
},
send: (data) => {
// ...
},
close: (code?: number, reason?: string) => {
// ...
},
get readyState() {
// ...
return readyState;
},
CONNECTING: 0,
OPEN: 1,
CLOSING: 2,
CLOSED: 3,
};
return socketTask;
}
}

// genAdapter函数创建adapter实体
// options 为 cloudbase.useAdapters(adapter, options) 时传入的参数
function genAdapter(options) {
const adapter: SDKAdapterInterface = {
// root对象为全局根对象,没有则填空对象{}
root: window,
reqClass: Request,
wsClass: WebSocket as WebSocketContructor,
localStorage: Storage,
// 首先缓存存放策略,建议始终保持localstorage
primaryStorage: StorageType.local,
// sessionStorage为可选项,如果平台不支持可不填
sessionStorage: sessionStorage,
};
return adapter;
}

// 三者缺一不可
const adapter = {
genAdapter,
isMatch,
// runtime标记平台唯一性
runtime: "平台名称",
};

export default adapter;

接入流程

第 1 步:安装并引入适配器

安装 @cloudbase/js-sdk 和所需平台的适配器,比如 QQ 小游戏平台:

# 安装 @cloudbase/js-sdk
npm i @cloudbase/js-sdk
# 安装 QQ 小游戏适配器
npm i cloudbase-adapter-qq_game

然后在业务代码中将引入适配器:

import cloudbase from "@cloudbase/js-sdk";
import adapter from "cloudbase-adapter-qq_game";

// options传入后,可以在 adapter 的 genAdapter 中获取到该参数
cloudbase.useAdapters(adapter, options);

第 2 步:配置安全应用来源

云开发需验证请求来源的合法性,常规 Web 通过验证安全域名,而由于上文提到的诸多类 Web 环境并没有域名的概念,所以需要借助安全应用凭证区分请求来源是否合法。

登录 云开发平台 ,在 安全来源 页面中的移动应用安全来源一栏,点击“添加应用”按钮,输入应用标识。

提示

应用标识必须是能够标记应用唯一性的信息,比如微信小程序的appId、移动应用的包名等。

添加成功后会创建一个安全应用的信息,如下图所示:

第 3 步:初始化云开发

在业务代码中初始化云开发时将第 2 步配置的安全应用信息作为参数传递给 init 方法:

import cloudbase from '@cloudbase/js-sdk';
import adapter from 'cloudbase-adapter-qq_game';

cloudbase.useAdapters(adapter);

cloudbase.init({
env: '环境ID',
appSign: '应用标识',
appSecret: {
appAccessKeyId: '应用凭证版本号'
appAccessKey: '应用凭证'
}
})
  • appSignstring,应用标识,对应上图中“应用标识”一栏;
  • appSecretObject,应用凭证信息,包括以下字段:
    • appAccessKeyIdstring,对应上图中“版本”一栏,同一个应用标识可以最多可以添加两个版本的凭证信息,以便区分开发和生产环境;
    • appAccessKeystring,对应上图中“操作”一栏点击“获取凭证”之后获取到的信息。
提示

appAccessKeyappAccessKeyId必须一一对应。

第 4 步:编写业务代码

经过以上准备工作之后便可以编写自身的业务代码。

一套代码多端适配

Web 和小程序无需配置

@cloudbase/js-sdk 已经内置了 Web 端和小程序端的 adapter,在这两个平台下无需配置即可使用。

如果您需要将一套代码兼容多种平台,@cloudbase/js-sdk 可以同时引入多个适配器,在运行时通过各适配器的isMatch函数判断平台类型,然后引入对应的兼容逻辑。比如以下代码可以同时兼容 QQ 小游戏、 Cocos 原生和百度小游戏三种平台:

import cloudbase from '@cloudbase/js-sdk';
import adapter as adapterOfQQGame from 'cloudbase-adapter-qq_game';
import adapter as adapterOfCocosNative from 'cloudbase-adapter-cocos_native';
import adapter as adapterOfBDGame from 'cloudbase-adapter-bd_game';

cloudbase.useAdapters([
adapterOfQQGame,
adapterOfCocosNative,
adapterOfBDGame
]);

验证码处理指南

1、概述

详细说明了处理验证码的完整流程,包括验证码触发场景、适配器改造、事件处理和用户交互。

2、验证码触发场景

验证码会在以下情况被要求输入:

  • 用户名密码登录失败 5 次后
  • 发送手机号或邮箱验证码时达到频率限制

需要验证码时,接口会返回如下错误信息

{
// ...
data: {
error: "captcha_required",
error_code: 4001,
error_description: "captcha_token required"
}
}

3、完整处理流程

适配器(Adapter)改造

// options 通过 useAdapters 第二参数传入
// 例如:cloudbase.useAdapters(adapter, options);
function genAdapter(options) {
const adapter: SDKAdapterInterface = {
// 其他适配器配置...

captchaOptions: {
// 当检测到需要验证码时,自动触发此函数
// url 为包含验证码图片、token、state等参数的字符串
openURIWithCallback: async (url: string) => {
// 解析URL中的验证码参数
const { captchaData, state, token } = cloudbase.parseCaptcha(url);

// 通过事件总线发送验证码数据,进行前端缓存及展示
options.EVENT_BUS.emit("CAPTCHA_DATA_CHANGE", {
captchaData, // Base64编码的验证码图片
state, // 验证码状态标识
token, // 验证码token
});

// 监听验证码校验结果
return new Promise((resolve) => {
console.log("等待验证码校验结果...");
options.EVENT_BUS.once("RESOLVE_CAPTCHA_DATA", (res) => {
// auth.verifyCaptchaData的校验结果
resolve(res);
});
});
},
},
};
return adapter;
}

初始化与适配器配置

// 创建事件总线实例
const EVENT_BUS = new EventBus();

// 配置并使用适配器,将 EVENT_BUS 注入给 genAdapter
cloudbase.useAdapters(adapter, { EVENT_BUS });
const app = cloudbase.init({
env: '环境ID',
appSign: '应用标识',
appSecret: {
appAccessKeyId: '应用凭证版本号'
appAccessKey: '应用凭证'
}
});
const auth = app.auth();

验证码界面实现

// 存储当前验证码状态
let captchaState = {
captchaData: "", // Base64编码的验证码图片
state: "", // 验证码状态标识
token: "", // 验证码token
};

// 监听验证码数据变化
EVENT_BUS.on("CAPTCHA_DATA_CHANGE", ({ captchaData, state, token }) => {
console.log("收到验证码数据", { captchaData, state, token });

// 更新本地验证码状态
captchaState = { captchaData, state, token };

// 在页面中显示验证码图片,例如在web使用img标签展示方式如下
document.getElementById('captcha-image').src = captchaData;
});

// 用户点击刷新验证码,触发该函数
const refreshCaptcha = async () => {
try {
// 获取最新验证码信息
const result = await auth.createCaptchaData({ state: captchaState.state });

// 更新本地验证码状态
captchaState = {
...captchaState
captchaData: result.data,
token: result.token,
};

// 更新显示的验证码,例如在web使用img标签展示方式如下
document.getElementById('captcha-image').src = result.data;
} catch (error) {
console.error("刷新验证码失败", error);
}
};

// 用户提交验证码,触发该函数
const verifyCaptchaData = async (userCaptcha) => {
try {
// 校验验证码
const verifyResult = await auth.verifyCaptchaData({
token: captchaState.token,
key: userCaptcha
});

// 将校验结果通知适配器
EVENT_BUS.emit("RESOLVE_CAPTCHA_DATA", verifyResult);

// 验证成功,继续登录流程
console.log("验证码校验成功");
} catch (error) {
console.error("验证码校验失败", error);
// 可以选择刷新验证码
await refreshCaptcha()
}
};

完整流程

  1. 用户尝试登录或发送验证码
  2. 若触发验证码要求,SDK 抛出 captcha_required 错误
  3. 适配器捕获错误并调用 openURIWithCallback
  4. 系统解析验证码参数并通过 EVENT_BUS 发送
  5. 前端展示验证码图片并等待用户输入
  6. 用户输入验证码并提交
  7. 系统验证并返回结果
  8. 根据验证结果决定是否重试原操作

验证码展示效果