以我们目前的开放平台为例。
Sandbox(沙盒)的在线文档可以在: https://stagingdoc.airdwing.com/ 找到。
Node.js版本SDK:https://github.com/AirDwing/node-airx-sdk
后端接口采用@airx/sdk
来完成AirX Open API
的反向代理.
const SDK = require('@airx/sdk');
const sdk = new SDK({
SecretId: 'xxxx',
SecretKey: 'xxxx',
// 访问的域名:
Domain: 'staging.airdwing.com',
// 是否启用HTTPS:
Secure: true
});
(async () => {
const result = await sdk.get('/user/check', {
username: '13212341234'
});
console.log(result);
})();
签名相关参数不用通过前端传递,防止AK/SK的泄露,签名交给Node.js
后端反代应用程序处理,所以以下几个签名相关参数不用传递:
由于反代应用程序支持 Cookie Session, 所以前端不用存储 Auth Token,也免去了 Auth Token被截获的风险.所以如果接口需要以下参数,不用传递:
每个会话都会生成一个随机唯一的模拟登录设备id, 如果接口需要以下参数, 不用传递:
但需要注意的是, 你需要调用 /guid
接口来查询本次会话的 guid
并进行登录设备绑定.
返回结果如下:
{
status: 1,
data: {
guid: 'xxxx'
}
}
webpack
等工具进行打包压缩, 将静态文件存放的目录建议的 dist
目录结构
位于:server/server.js
app.use(async (ctx, next) => {
ctx.api = await swagger();
const path = ctx.api.paths[ctx.path];
// ! 仅供开发测试, 允许跨域操作很危险
if (ENV === 'development') {
ctx.set('Access-Control-Allow-Origin', '*');
}
if (path === undefined) {
// 前后端分离, 处理前端相关静态文件
try {
await send(ctx, ctx.path, { root: `${__dirname}/../dist` });
} catch (err) {
ctx.status = 404;
// 注意要添加 404.html 到 dist 目录
// await send(ctx, '/404.html', { root: `${__dirname}/../dist` });
}
return;
}
await next();
});
位于:server/server.js
app.use(async (ctx) => {
// 提供 guid 查询接口
if (ctx.path === '/guid') {
let guid = ctx.session.guid;
if (isEmpty(guid)) {
guid = uuid();
ctx.session.guid = guid;
}
ctx.status = 200;
ctx.body = { status: 1, data: { guid } };
return;
}
// 处理后端接口
// 封装sdk请求
if (ctx.path === '/upload') {
// 处理上传
await upload(ctx);
} else {
// 处理其他接口
await others(ctx);
}
});
位于:server/lib/swagger.js
const getSwagger = async () => {
const result = await request({
method: 'GET',
url: apiOptions.doc,
timeout: 5000
});
const paths = result.paths;
const app = {
host: result.host,
paths: Object.keys(paths).reduce((p, x) => {
/* eslint no-param-reassign:0 */
const method = Object.keys(paths[x])[0];
p[x] = paths[x][method].parameters.map(t => t.name);
return p;
}, {})
};
return app;
};
module.exports = async () => {
let app = await redis.get('app');
if (isEmpty(app)) {
app = await getSwagger();
await redis.set('app', app);
}
return app;
};
通过 Swagger
配置文件可以将接口列表,及各个接口需要的参数,以便于后边再接口调用的时候有针对性的处理参数。
位于:server/handler/others.js
module.exports = async (ctx) => {
const method = ctx.request.method.toLowerCase();
const receivedParams = method === 'get' ? ctx.query : await parse(ctx.req);
const sdk = new SDK({
SecretId: apiOptions.ak,
SecretKey: apiOptions.sk,
Domain: ctx.api.host,
Secure: apiOptions.scheme === 'https'
});
// 处理请求参数
const params = ctx.api.paths[ctx.path];
if (params.indexOf('auth') !== -1) {
// 处理需要 登录 的接口
const auth = ctx.session.auth;
if (isEmpty(auth)) {
ctx.status = 200;
ctx.body = { status: 0, code: 401 };
return;
}
const ttl = ~~ctx.session.ttl;
// 处理登录超时(1小时),提前10分钟重新获取auth
if (ttl - getTimestamp() < 600) {
const tmpParams = JSON.parse(ctx.session.params);
const login = await sdk.post('/user/login', tmpParams);
const loginResult = doLogin(ctx, login, tmpParams);
// 密码被修改等无法登录
if (loginResult === -1) {
ctx.status = 200;
ctx.body = { status: 0, code: 401 };
return;
}
}
receivedParams.auth = auth;
}
if (params.indexOf('guid') !== -1) {
// 处理需要 guid 的接口
let guid = ctx.session.guid;
if (isEmpty(guid)) {
guid = uuid();
ctx.session.guid = guid;
}
receivedParams.guid = guid;
}
if (params.indexOf('device') !== -1) {
// 处理需要登录设备名称的接口
receivedParams.device = 'AirX网页版';
}
if (params.indexOf('password') !== -1) {
// 处理需要 authcode加密 的接口
receivedParams.key = randStr(6);
receivedParams.passwod = encode(receivedParams.passwod, receivedParams.key);
}
const result = await sdk[method](ctx.path, receivedParams);
// 记录登录信息
if (ctx.path === '/user/login') {
doLogin(ctx, result, receivedParams);
}
ctx.status = 200;
ctx.body = result;
};
该实例代码可以在: https://github.com/AirDwing/node-airx-webapp 找到。