跳到主要内容

开发示例

本文档展示如何在 Node.js 环境中快速接入 CloudPSS XStudio GraphQL API,完成:

  • 登录认证
  • 查询系统与账户信息
  • 查询 / 提交 / 监控任务
  • 批量任务
提示

本文采用 ES Module 写法(import / export),建议 Node.js 版本 ≥ 18。

前置条件

  • Node.js ≥ 18
  • 已注册的 CloudPSS 账户(用户名 / 密码)
  • 可以访问的 GraphQL 接口地址(默认示例为 https://cloudpss.net/graphql

安装依赖

mkdir cloudpss-graphql-demo
cd cloudpss-graphql-demo

# 初始化项目
npm init -y

# 安装依赖
npm install graphql-request dotenv

环境配置

  1. 创建 .env 文件
CLOUDPSS_GQL_URL=https://cloudpss.net/graphql
CLOUDPSS_USERNAME=your_username
CLOUDPSS_PASSWORD=your_password
注意

实际项目中请使用更安全的方式管理凭据(如 CI/CD 密钥管理、环境变量注入等),不要把 .env 提交到公开仓库。

  1. 创建 package.json 文件
{
"type": "module",
"scripts": {
"start": "node example.js",
"advanced": "node advanced-examples.js"
}
}

推荐目录结构

cloudpss-graphql-demo/
├─ .env
├─ package.json
├─ cloudpss-client.js # 基础 GraphQL 客户端封装
├─ example.js # 快速开始示例
├─ advanced-examples.js # 高级功能示例(可选)
└─ utils.js # 工具函数

代码示例

工具函数(utils.js)

// utils.js

// 构建资源ID,例如:application/userName/myApp
export function buildResourceId(type, owner, key) {
return `${type}/${owner}/${key}`;
}

// 解析资源ID
export function parseResourceId(rid) {
const parts = rid.split('/');
if (parts.length !== 3) {
throw new Error('Invalid resource ID format');
}
return {
type: parts[0],
owner: parts[1],
key: parts[2],
};
}

// 格式化任务状态(转为中文描述)
export function formatJobStatus(status) {
const statusMap = {
WAITING: '等待中',
RUNNING: '运行中',
COMPLETED: '已完成',
FAILED: '失败',
ABORTED: '已中止',
CANCELED: '已取消',
TIMED_OUT: '超时',
ABORTING: '中止中',
KILLED: '被终止',
};
return statusMap[status] || status;
}

// GraphQL 错误处理器(将多条 error 合并到 message 中)
export function handleGraphQLError(error) {
if (error.response && error.response.errors) {
const messages = error.response.errors.map((err) => err.message).join(', ');
throw new Error(`GraphQL Error: ${messages}`);
}
throw error;
}

GraphQL API 客户端实现(cloudpss-client.js)

// cloudpss-client.js
import 'dotenv/config';
import { GraphQLClient } from 'graphql-request';
import { handleGraphQLError } from './utils.js';

class CloudPSSClient {
constructor() {
this.apiUrl = process.env.CLOUDPSS_GQL_URL;
if (!this.apiUrl) {
throw new Error('CLOUDPSS_GQL_URL 未配置');
}
this.client = new GraphQLClient(this.apiUrl);
this.token = null;
}

// 身份验证
async authenticate(username, password) {
try {
// 1. 创建账户质询
const challengeQuery = `
query accountChallenge($input: AccountChallengeInput!) {
accountChallenge(input: $input) {
id
groups {
items {
id
type
}
}
}
}
`;

const challengeResult = await this.client.request(challengeQuery, {
input: {
name: username,
},
});

if (!challengeResult.accountChallenge) {
throw new Error('未获取到账户质询信息');
}

const challengeGroups = challengeResult.accountChallenge.groups ?? [];
const firstGroup = challengeGroups[0];

if (!firstGroup || !Array.isArray(firstGroup.items)) {
throw new Error('未找到有效的质询分组');
}

const passwordItem = firstGroup.items.find(
(item) => item.type === 'PASSWORD',
);

if (!passwordItem) {
throw new Error('当前账户未配置密码登录方式');
}

const challengeId = passwordItem.id;

// 2. 响应质询获取令牌
const tokenQuery = `
mutation CreateToken($input: CreateAccountTokenInput!) {
createAccountToken(input: $input) {
token
expirationTime
user {
name
displayName
}
}
}
`;

const tokenResult = await this.client.request(tokenQuery, {
input: {
id: challengeResult.accountChallenge.id,
answers: [
{
id: challengeId,
answer: { password },
},
],
},
});

if (!tokenResult.createAccountToken?.token) {
throw new Error('获取账号令牌失败');
}

this.token = tokenResult.createAccountToken.token;

// 设置认证头
this.client.setHeader('Authorization', `Bearer ${this.token}`);

return tokenResult.createAccountToken;
} catch (error) {
handleGraphQLError(error);
}
}

// 获取系统信息
async getSystemInfo() {
const query = `
query SystemInfo {
systemInfo {
machineId
}
}
`;

return await this.client.request(query);
}

// 查询用户信息
async getAccount(name, type = 'USER') {
const query = `
query GetAccount($input: AccountInput!) {
account(input: $input) {
name
displayName
type
... on AccountUser {
email
phone
bio
company
}
}
}
`;

return await this.client.request(query, {
input: { name, type },
});
}

// 查询应用程序列表
async getApplications(options = {}) {
const query = `
query GetApplications($input: ApplicationsInput!) {
applications(input: $input) {
items {
rid
name
description
owner
updatedAt
tags
}
cursor
count
total
}
}
`;

const input = {
orderBy: options.orderBy || ['updatedAt>'],
limit: options.limit || 20,
...options,
};

return await this.client.request(query, { input });
}

// 创建应用程序
async createApplication(appData) {
const mutation = `
mutation CreateApplication($input: CreateApplicationInput!) {
createApplication(input: $input) {
rid
name
description
owner
}
}
`;

return await this.client.request(mutation, {
input: appData,
});
}

// 提交任务
async submitJob(jobData) {
const mutation = `
mutation CreateJob($input: CreateJobInput!) {
createJob(input: $input) {
id
status
createTime
user
}
}
`;

return await this.client.request(mutation, {
input: jobData,
});
}

// 查询任务状态
async getJobStatus(jobId) {
const query = `
query GetJob($input: JobInput!) {
job(input: $input) {
id
status
createTime
startTime
endTime
args
context
priority
}
}
`;

return await this.client.request(query, {
input: { id: jobId },
});
}

// 查询任务列表
async getJobs(options = {}) {
const query = `
query GetJobs($input: JobsInput!) {
jobs(input: $input) {
items {
id
status
createTime
startTime
endTime
user
}
cursor
count
total
}
}
`;

const input = {
orderBy: options.orderBy || ['createTime<'],
limit: options.limit || 20,
...options,
};

return await this.client.request(query, { input });
}

// 终止任务
async abortJob(jobId, timeout = 30) {
const mutation = `
mutation AbortJob($input: AbortJobInput!) {
abortJob(input: $input) {
id
status
}
}
`;

return await this.client.request(mutation, {
input: { id: jobId, timeout },
});
}
}

export default CloudPSSClient;

快速开始示例(example.js)

// example.js
import CloudPSSClient from './cloudpss-client.js';
import { formatJobStatus } from './utils.js';

async function main() {
const client = new CloudPSSClient();

try {
// 1. 身份验证
console.log('正在登录...');
const authResult = await client.authenticate(
process.env.CLOUDPSS_USERNAME,
process.env.CLOUDPSS_PASSWORD,
);
console.log('登录成功:', authResult.user.displayName);

// 2. 获取系统信息
console.log('\n获取系统信息...');
const systemInfo = await client.getSystemInfo();
console.log('系统信息:', systemInfo.systemInfo);

// 3. 查询应用程序列表
console.log('\n查询应用程序列表...');
const apps = await client.getApplications({ limit: 5 });
console.log(`找到 ${apps.applications.total} 个应用程序:`);
apps.applications.items.forEach((app) => {
console.log(`- ${app.name} (${app.rid})`);
});

// 4. 查询当前用户的任务
console.log('\n查询任务列表...');
const user = authResult.user.name;
const jobs = await client.getJobs({
limit: 10,
user: ['or', user],
});
console.log(`找到 ${jobs.jobs.total} 个任务:`);
jobs.jobs.items.forEach((job) => {
console.log(
`- 任务 ${job.id}: ${formatJobStatus(job.status)} (${new Date(
job.createTime,
).toLocaleString()})`,
);
});

// 5. 提交新任务示例
console.log('\n提交新任务...');
const newJob = await client.submitJob({
context: ['function/CloudPSS/hello'],
args: {
message: 'Hello from Node.js!',
timestamp: Date.now(),
},
policy: {
queue: 1,
tres: {
cpu: 1,
ecpu: 0,
mem: 0,
},
priority: 0,
},
});

console.log('任务已提交:', newJob.createJob);

// 6. 监控任务状态(简单轮询)
console.log('\n监控任务状态...');
const jobId = newJob.createJob.id;
let completed = false;

while (!completed) {
const jobStatus = await client.getJobStatus(jobId);

console.log(
`任务 ${jobId} 状态: ${formatJobStatus(jobStatus.job.status)}`,
);

if (
['COMPLETED', 'FAILED', 'ABORTED', 'CANCELED'].includes(
jobStatus.job.status,
)
) {
completed = true;
console.log('任务执行完成');
} else {
// 等待 5 秒后再次检查
await new Promise((resolve) => setTimeout(resolve, 5000));
}
}
} catch (error) {
console.error('错误:', error.message);
if (error.response) {
console.error('响应详情:', error.response.errors);
}
}
}


// 运行示例
main().catch(console.error);

运行快速开始示例

npm run start
运行快速开始示例结果
运行快速开始示例结果

常见问题

报错 SyntaxError: Cannot use import statement outside a module?
请确认 package.json 中包含 "type": "module",并使用 node example.js 命令可以直接运行。
提示 CLOUDPSS_GQL_URL 未配置?

检查 .env 是否存在且与项目根目录同级;运行前确保环境变量已被 dotenv 正确加载(本示例在 cloudpss-client.js 中使用了 import 'dotenv/config')。