跨端开发
@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: '应用凭证'
}
})
appSign
:string
,应用标识,对应上图中“应用标识”一栏;appSecret
:Object
,应用凭证信息,包括以下字段:appAccessKeyId
:string
,对应上图中“版本”一栏,同一个应用标识可以最多可以添加两个版本的凭证信息,以便区分开发和生产环境;appAccessKey
:string
,对应上图中“操作”一栏点击“获取凭证”之后获取到的信息。
appAccessKey
和appAccessKeyId
必须一一对应。
第 4 步:编写业务代码
经过以上准备工作之后便可以编写自身的业务代码。
一套代码多端适配
@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()
}
};
完整流程
- 用户尝试登录或发送验证码
- 若触发验证码要求,SDK 抛出 captcha_required 错误
- 适配器捕获错误并调用 openURIWithCallback
- 系统解析验证码参数并通过 EVENT_BUS 发送
- 前端展示验证码图片并等待用户输入
- 用户输入验证码并提交
- 系统验证并返回结果
- 根据验证结果决定是否重试原操作
验证码展示效果
