first commit
commit
95eda06dbc
|
|
@ -0,0 +1,41 @@
|
|||
# Dependencies
|
||||
node_modules/
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
build/
|
||||
*.tgz
|
||||
|
||||
# IDE and editor files
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
.DS_Store
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Runtime data
|
||||
pids/
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Testing
|
||||
coverage/
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
temp/
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
var crypto = require('crypto');
|
||||
|
||||
var randomInt = Math.floor(Math.random() * 0xFFFFFFFF);
|
||||
|
||||
const VERSION = "001";
|
||||
const VERSION_LENGTH = 3;
|
||||
|
||||
const APP_ID_LENGTH = 24;
|
||||
|
||||
privileges = {
|
||||
PrivPublishStream: 0,
|
||||
|
||||
// not exported, do not use directly
|
||||
privPublishAudioStream: 1,
|
||||
privPublishVideoStream: 2,
|
||||
privPublishDataStream: 3,
|
||||
|
||||
PrivSubscribeStream: 4,
|
||||
};
|
||||
|
||||
|
||||
module.exports.privileges = privileges;
|
||||
|
||||
// Initializes token struct by required parameters.
|
||||
var AccessToken = function (appID, appKey, roomID, userID) {
|
||||
let token = this;
|
||||
this.appID = appID;
|
||||
this.appKey = appKey;
|
||||
this.roomID = roomID;
|
||||
this.userID = userID;
|
||||
this.issuedAt = Math.floor(new Date() / 1000);
|
||||
this.nonce = randomInt;
|
||||
this.expireAt = 0;
|
||||
this.privileges = {};
|
||||
|
||||
// AddPrivilege adds permission for token with an expiration.
|
||||
this.addPrivilege = function (privilege, expireTimestamp) {
|
||||
if (token.privileges === undefined) {
|
||||
token.privileges = {}
|
||||
}
|
||||
token.privileges[privilege] = expireTimestamp;
|
||||
|
||||
if (privilege === privileges.PrivPublishStream) {
|
||||
token.privileges[privileges.privPublishVideoStream] = expireTimestamp;
|
||||
token.privileges[privileges.privPublishAudioStream] = expireTimestamp;
|
||||
token.privileges[privileges.privPublishDataStream] = expireTimestamp;
|
||||
}
|
||||
};
|
||||
|
||||
// ExpireTime sets token expire time, won't expire by default.
|
||||
// The token will be invalid after expireTime no matter what privilege's expireTime is.
|
||||
this.expireTime = function (expireTimestamp) {
|
||||
token.expireAt = expireTimestamp;
|
||||
};
|
||||
|
||||
this.packMsg = function () {
|
||||
var bufM = new ByteBuf();
|
||||
bufM.putUint32(token.nonce);
|
||||
bufM.putUint32(token.issuedAt);
|
||||
bufM.putUint32(token.expireAt);
|
||||
bufM.putString(token.roomID);
|
||||
bufM.putString(token.userID);
|
||||
bufM.putTreeMapUInt32(token.privileges);
|
||||
return bufM.pack()
|
||||
};
|
||||
|
||||
// Serialize generates the token string
|
||||
this.serialize = function () {
|
||||
var bytesM = this.packMsg();
|
||||
|
||||
var signature = encodeHMac(token.appKey, bytesM);
|
||||
var content = new ByteBuf().putBytes(bytesM).putBytes(signature).pack();
|
||||
|
||||
return (VERSION + token.appID + content.toString('base64'));
|
||||
};
|
||||
|
||||
// Verify checks if this token valid, called by server side.
|
||||
this.verify = function (key) {
|
||||
if (token.expireAt > 0 && Math.floor(new Date() / 1000) > token.expireAt) {
|
||||
return false
|
||||
}
|
||||
|
||||
token.appKey = key;
|
||||
return encodeHMac(token.appKey, this.packMsg()).toString() === token.signature;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Parse retrieves token information from raw string
|
||||
var Parse = function (raw) {
|
||||
try {
|
||||
if (raw.length <= VERSION_LENGTH + APP_ID_LENGTH) {
|
||||
return
|
||||
}
|
||||
if (raw.substr(0, VERSION_LENGTH) !== VERSION) {
|
||||
return
|
||||
}
|
||||
var token = new AccessToken("", "", "", "");
|
||||
token.appID = raw.substr(VERSION_LENGTH, APP_ID_LENGTH);
|
||||
|
||||
var contentBuf = Buffer.from(raw.substr(VERSION_LENGTH + APP_ID_LENGTH), 'base64');
|
||||
var readbuf = new ReadByteBuf(contentBuf);
|
||||
|
||||
var msg = readbuf.getString();
|
||||
token.signature = readbuf.getString().toString();
|
||||
|
||||
// parse msg
|
||||
var msgBuf = new ReadByteBuf(msg);
|
||||
token.nonce = msgBuf.getUint32();
|
||||
token.issuedAt = msgBuf.getUint32();
|
||||
token.expireAt = msgBuf.getUint32();
|
||||
token.roomID = msgBuf.getString().toString();
|
||||
token.userID = msgBuf.getString().toString();
|
||||
token.privileges = msgBuf.getTreeMapUInt32();
|
||||
|
||||
return token
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports.version = VERSION;
|
||||
module.exports.AccessToken = AccessToken;
|
||||
module.exports.Parse = Parse;
|
||||
|
||||
var encodeHMac = function (key, message) {
|
||||
return crypto.createHmac('sha256', key).update(message).digest();
|
||||
};
|
||||
|
||||
var ByteBuf = function () {
|
||||
var that = {
|
||||
buffer: Buffer.alloc(1024)
|
||||
, position: 0
|
||||
};
|
||||
|
||||
// that.buffer.fill(0);
|
||||
|
||||
that.pack = function () {
|
||||
var out = Buffer.alloc(that.position);
|
||||
that.buffer.copy(out, 0, 0, out.length);
|
||||
return out;
|
||||
};
|
||||
|
||||
that.putUint16 = function (v) {
|
||||
that.buffer.writeUInt16LE(v, that.position);
|
||||
that.position += 2;
|
||||
return that;
|
||||
};
|
||||
|
||||
that.putUint32 = function (v) {
|
||||
that.buffer.writeUInt32LE(v, that.position);
|
||||
that.position += 4;
|
||||
return that;
|
||||
};
|
||||
|
||||
that.putBytes = function (bytes) {
|
||||
that.putUint16(bytes.length);
|
||||
bytes.copy(that.buffer, that.position);
|
||||
that.position += bytes.length;
|
||||
return that;
|
||||
};
|
||||
|
||||
that.putString = function (str) {
|
||||
return that.putBytes(Buffer.from(str));
|
||||
};
|
||||
|
||||
that.putTreeMap = function (map) {
|
||||
if (!map) {
|
||||
that.putUint16(0);
|
||||
return that;
|
||||
}
|
||||
|
||||
that.putUint16(Object.keys(map).length);
|
||||
for (var key in map) {
|
||||
that.putUint16(key);
|
||||
that.putString(map[key]);
|
||||
}
|
||||
|
||||
return that;
|
||||
};
|
||||
|
||||
that.putTreeMapUInt32 = function (map) {
|
||||
if (!map) {
|
||||
that.putUint16(0);
|
||||
return that;
|
||||
}
|
||||
|
||||
that.putUint16(Object.keys(map).length);
|
||||
for (var key in map) {
|
||||
that.putUint16(key);
|
||||
that.putUint32(map[key]);
|
||||
}
|
||||
|
||||
return that;
|
||||
};
|
||||
|
||||
return that;
|
||||
};
|
||||
|
||||
var ReadByteBuf = function (bytes) {
|
||||
var that = {
|
||||
buffer: bytes
|
||||
, position: 0
|
||||
};
|
||||
|
||||
that.getUint16 = function () {
|
||||
var ret = that.buffer.readUInt16LE(that.position);
|
||||
that.position += 2;
|
||||
return ret;
|
||||
};
|
||||
|
||||
that.getUint32 = function () {
|
||||
var ret = that.buffer.readUInt32LE(that.position);
|
||||
that.position += 4;
|
||||
return ret;
|
||||
};
|
||||
|
||||
that.getString = function () {
|
||||
var len = that.getUint16();
|
||||
|
||||
var out = Buffer.alloc(len);
|
||||
that.buffer.copy(out, 0, that.position, (that.position + len));
|
||||
that.position += len;
|
||||
return out;
|
||||
};
|
||||
|
||||
that.getTreeMapUInt32 = function () {
|
||||
var map = {};
|
||||
var len = that.getUint16();
|
||||
for (var i = 0; i < len; i++) {
|
||||
var key = that.getUint16();
|
||||
var value = that.getUint32();
|
||||
map[key] = value;
|
||||
}
|
||||
return map;
|
||||
};
|
||||
|
||||
return that;
|
||||
};
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
/**
|
||||
* Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved.
|
||||
* SPDX-license-identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
const Koa = require('koa');
|
||||
const bodyParser = require('koa-bodyparser');
|
||||
const cors = require('koa2-cors');
|
||||
const { Signer } = require('@volcengine/openapi');
|
||||
const fetch = require('node-fetch');
|
||||
const AccessToken = require('./AccessToken');
|
||||
const Privileges = require('./AccessToken').privileges;
|
||||
|
||||
const app = new Koa();
|
||||
|
||||
app.use(cors({
|
||||
origin: '*'
|
||||
}));
|
||||
|
||||
/**
|
||||
* @notes 在 https://console.volcengine.com/iam/keymanage/ 获取 AK/SK
|
||||
*/
|
||||
const ACCOUNT_INFO = {
|
||||
/**
|
||||
* @notes 必填, 在 https://console.volcengine.com/iam/keymanage/ 获取
|
||||
*/
|
||||
accessKeyId: 'AKLTNGJhYTRiMWE5Yzc2NDhlODk3NzE0YTI4NzEyZTM3NDI',
|
||||
/**
|
||||
* @notes 必填, 在 https://console.volcengine.com/iam/keymanage/ 获取
|
||||
*/
|
||||
secretKey: 'T1RrellUVXpaRFU0WXpVME5EZzRNV0UxTXpObFl6QmhPVFl4TUdFME0yTQ==',
|
||||
/**
|
||||
* @notes 非必填, 主账号无须传入, 子账号须传, 获取方式可参考
|
||||
* https://www.volcengine.com/docs/6348/1315561 中的 步骤 4-使用子账号调用智能体接口 一节
|
||||
*/
|
||||
// sessionToken: 'Your SessionToken',
|
||||
}
|
||||
|
||||
// AccessToken 应用配置
|
||||
const appID = "67e11a296ff39301ed7429aa";
|
||||
const appKey = "51151bf3ab2545bbb1b1494ffaae0521";
|
||||
|
||||
app.use(bodyParser());
|
||||
|
||||
|
||||
app.use(async ctx => {
|
||||
/**
|
||||
* @brief 代理 AIGC 的 OpenAPI 请求
|
||||
*/
|
||||
if (ctx.url.startsWith('/api/proxyaigc') && ctx.method.toLowerCase() === 'post') {
|
||||
const { Action, Version = '2023-11-01' } = ctx.query || {};
|
||||
const body = {
|
||||
AppId: appID, // 自动添加 AppId
|
||||
...ctx.request.body
|
||||
};
|
||||
|
||||
const openApiRequestData = {
|
||||
region: 'cn-north-1',
|
||||
method: 'POST',
|
||||
params: {
|
||||
Action,
|
||||
Version,
|
||||
},
|
||||
headers: {
|
||||
Host: 'rtc.volcengineapi.com',
|
||||
'Content-type': 'application/json',
|
||||
Authorization: '',
|
||||
},
|
||||
body,
|
||||
};
|
||||
const signer = new Signer(openApiRequestData, "rtc");
|
||||
signer.addAuthorization(ACCOUNT_INFO);
|
||||
|
||||
console.log('请求参数:', { Action, Version, body }); // 添加日志
|
||||
|
||||
const result = await fetch(`https://rtc.volcengineapi.com?Action=${Action}&Version=${Version}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...openApiRequestData.headers,
|
||||
Authorization: openApiRequestData.headers.Authorization,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
const volcResponse = await result.json();
|
||||
console.log('响应结果:', volcResponse); // 添加日志
|
||||
|
||||
if (!volcResponse.ResponseMetadata?.Error) {
|
||||
ctx.body = volcResponse;
|
||||
} else {
|
||||
ctx.status = 500;
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: volcResponse.ResponseMetadata.Error.Message || '请求失败',
|
||||
data: null
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @brief 生成 token 的 API 接口
|
||||
*/
|
||||
else if (ctx.url === '/api/token' && ctx.method.toLowerCase() === 'post') {
|
||||
try {
|
||||
const { roomId, userId } = ctx.request.body;
|
||||
|
||||
// 参数验证
|
||||
if (!roomId || !userId) {
|
||||
ctx.status = 400;
|
||||
ctx.body = {
|
||||
code: 400,
|
||||
message: '缺少必要参数 roomId 或 userId',
|
||||
data: null
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建 AccessToken
|
||||
const key = new AccessToken.AccessToken(appID, appKey, roomId, userId);
|
||||
|
||||
// 添加权限
|
||||
key.addPrivilege(Privileges.PrivSubscribeStream, 0);
|
||||
key.addPrivilege(Privileges.PrivPublishStream, Math.floor(new Date() / 1000) + (24 * 3600));
|
||||
key.expireTime(Math.floor(new Date() / 1000) + (24 * 3600));
|
||||
|
||||
// 序列化 token
|
||||
const token = key.serialize();
|
||||
|
||||
// 返回成功响应
|
||||
ctx.status = 200;
|
||||
ctx.body = {
|
||||
code: 200,
|
||||
message: '获取 token 成功',
|
||||
data: { token }
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('生成 token 出错:', error);
|
||||
ctx.status = 500;
|
||||
ctx.body = {
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
data: null
|
||||
};
|
||||
}
|
||||
} else {
|
||||
ctx.body = '<h1>404 Not Found</h1>';
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(3001, () => {
|
||||
console.log('AIGC Server is running at http://localhost:3001');
|
||||
console.log('Token API is available at http://localhost:3001/api/token');
|
||||
});
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "AIGCServer",
|
||||
"version": "1.0.0",
|
||||
"description": "Server for demo to call open api",
|
||||
"main": "app.js",
|
||||
"license": "BSD-3-Clause",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@volcengine/openapi": "^1.22.0",
|
||||
"AIGCServer": "file:",
|
||||
"express": "^4.21.2",
|
||||
"koa": "^2.15.3",
|
||||
"koa-bodyparser": "^4.4.1",
|
||||
"koa2-cors": "^2.0.6",
|
||||
"node-fetch": "^2.3.2"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "node app.js"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue