// const request = require('./request.js');
// // 华为云配置(需替换为实际值)
// const config = {
// iamEndpoint: 'https://iam.cn-south-1.myhuaweicloud.com',
// iotEndpoint: 'https://iotda.cn-south-1.myhuaweicloud.com',
// credentials: {
// username: 'KTD',
// password: 'asd456852',
// domain: {
// name: 'hid_k2jbvvc8omizoea',
// id: '1c6efe1c939a4665900226ba17663bbc' // 必须添加
// }
// }
// };
// // 全局Token缓存
// let tokenCache = {
// token: null,
// expiresAt: 0
// };
// // 获取IAM Token(官方推荐方式)
// async function getIamToken() {
// const url = `${config.iamEndpoint}/v3/auth/tokens`;
// const authData = {
// auth: {
// identity: {
// methods: ["password"],
// password: {
// user: {
// name: config.credentials.username,
// password: config.credentials.password,
// domain: {
// name: config.credentials.domain.name
// }
// }
// }
// },
// scope: {
// domain: {
// id: config.credentials.domain.id // 关键修改点
// }
// }
// }
// };
// const headers = {
// 'Content-Type': 'application/json',
// 'X-Domain-Id': config.credentials.domain.id, // 必须添加
// 'X-Language': 'zh-cn'
// };
// try {
// const response = await request.post(url, authData, headers, true);
// return response.header['X-Subject-Token'];
// } catch (error) {
// console.error('Token获取失败:', {
// request: { url, data: authData },
// response: error.response?.data
// });
// throw error;
// }
// }
// // 查询设备消息(带自动Token管理)
// async function queryDeviceMessages(deviceId) {
// const token = await getIamToken();
// const url = `${config.iotEndpoint}/v5/iot/${config.projectId}/devices/${deviceId}/messages`;
// return await request.get(url, {}, {
// 'Content-Type': 'application/json',
// 'X-Auth-Token': token
// });
// }
// // 下发设备命令
// async function sendDeviceCommand(deviceId, serviceId, command) {
// const token = await getIamToken();
// const url = `${config.iotEndpoint}/v5/iot/${config.projectId}/devices/${deviceId}/commands`;
// return await request.post(url, {
// service_id: serviceId,
// command_name: 'control',
// paras: { value: command }
// }, {
// 'Content-Type': 'application/json',
// 'X-Auth-Token': token
// });
// }
// module.exports = {
// getIamToken,
// queryDeviceMessages,
// sendDeviceCommand
// };
// // iam-iot.js(或你的小程序里对应的文件名)
// const request = require('./request.js'); // 你自己的封装模块
// // —— 一、华为云配置 ——
// // 请务必替换以下各项为你项目/账号下的真实值
// const config = {
// iamEndpoint: 'https://iam.cn-south-1.myhuaweicloud.com',
// iotEndpoint: 'https://iotda.cn-south-1.myhuaweicloud.com',
// projectId: '67f73bd1b696b3426f2f3d0a', // ← 新增:在 IoTDA 里创建项目后会拿到
// credentials: {
// username: 'KTD', // 你的 IAM 用户名
// password: 'asd456852', // 你的 IAM 用户密码
// domain: {
// name: 'hid_k2jbvvc8omizoea', // 你的 IAM 域名称
// id: '1c6efe1c939a4665900226ba17663bbc' // 你的 IAM 域 ID(必填)
// }
// }
// };
// // —— 二、全局 Token 缓存对象 ——
// // 用来在有效期内重复使用同一个 IAM Token,减少不必要的网络请求
// let tokenCache = {
// token: null, // 当前缓存的 X-Subject-Token
// expiresAt: 0 // UNIX 时间戳(毫秒),表示 token 的过期时间
// };
// /**
// * 将华为云返回的 ISO8601 时间字符串(如 "2025-06-01T15:28:31.000Z")转换为 JS 的毫秒时间戳
// */
// function parseIsoToTimestamp(isoStr) {
// return new Date(isoStr).getTime();
// }
// /**
// * 获取(或从缓存返回)IAM Token
// * - 如果缓存里 token 还没过期,则直接返回它
// * - 否则调用 /v3/auth/tokens 接口重新获取(Domain 级别)
// */
// async function getIamToken() {
// // 1. 如果缓存里已有 token 且未过期,直接复用
// const now = Date.now();
// if (tokenCache.token && tokenCache.expiresAt > now + 2 * 1000) {
// // 提前 2 秒作为缓冲,防止刚好到点就过期的情况
// return tokenCache.token;
// }
// // 2. 缓存失效或不存在,需要重新请求
// const url = `${config.iamEndpoint}/v3/auth/tokens`;
// const authData = {
// auth: {
// identity: {
// methods: ["password"],
// password: {
// user: {
// name: config.credentials.username,
// password: config.credentials.password,
// domain: {
// name: config.credentials.domain.name
// }
// }
// }
// },
// scope: {
// domain: {
// id: config.credentials.domain.id
// }
// }
// }
// };
// // 3. 构造必要的请求头(只需 Content-Type、X-Domain-Id、X-Language)
// const headers = {
// 'Content-Type': 'application/json;charset=UTF-8',
// 'X-Domain-Id': config.credentials.domain.id,
// 'X-Language': 'zh-cn'
// };
// try {
// // 注意:request.post 方法的最后一个参数若为 true,表明需要拿到完整 response(含 header & body)
// const response = await request.post(url, authData, headers, true);
// // 4. 从响应头中取到 Token
// const newToken = response.header['X-Subject-Token'];
// if (!newToken) {
// throw new Error("IAM 返回未携带 X-Subject-Token");
// }
// // 5. 从响应体里读取 token.expires_at 字段,计算过期时间戳
// // 假设 response.data.token.expires_at 是类似 "2025-06-01T15:28:31.000Z" 的字符串
// const respBody = response.data;
// const expiresAtIso = respBody.token && respBody.token.expires_at;
// if (!expiresAtIso) {
// // 如果后端没有返回 expires_at,也可以直接缓存 1 小时后再刷新
// console.warn("未从 IAM 响应体里读取到 expires_at,默认缓存 3600 秒");
// tokenCache.expiresAt = now + 3600 * 1000;
// } else {
// tokenCache.expiresAt = parseIsoToTimestamp(expiresAtIso);
// }
// // 6. 更新全局缓存并返回
// tokenCache.token = newToken;
// return newToken;
// } catch (err) {
// console.error('IAM Token 获取失败:', {
// request: { url, data: authData, headers },
// error: err.response?.data || err.message
// });
// throw err;
// }
// }
// /**
// * 查询设备消息(自带自动 Token 管理)
// * @param {string} deviceId 设备 ID
// */
// async function queryDeviceMessages(deviceId) {
// const token = await getIamToken();
// const url = `${config.iotEndpoint}/v5/iot/${config.projectId}/devices/${deviceId}/messages`;
// const headers = {
// 'Content-Type': 'application/json;charset=UTF-8',
// 'X-Auth-Token': token
// };
// try {
// return await request.get(url, {}, headers);
// } catch (err) {
// console.error('查询设备消息失败:', {
// url,
// deviceId,
// error: err.response?.data || err.message
// });
// throw err;
// }
// }
// /**
// * 下发设备命令(自带自动 Token 管理)
// * @param {string} deviceId 设备 ID
// * @param {string} serviceId 服务 ID(对应在 IoTDA 控制台定义的能力服务 ID)
// * @param {any} command 下发给设备的参数内容,比如 { value: true } 或其他 JSON 对象
// */
// async function sendDeviceCommand(deviceId, serviceId, command) {
// const token = await getIamToken();
// const url = `${config.iotEndpoint}/v5/iot/${config.projectId}/devices/${deviceId}/commands`;
// const body = {
// service_id: serviceId,
// command_name: 'control', // 如果你在控制台定义的命令名不是固定 "control",请替换为实际命令名称
// paras: command // 直接把用户传进来的 command 对象放在 paras 下
// };
// const headers = {
// 'Content-Type': 'application/json;charset=UTF-8',
// 'X-Auth-Token': token
// };
// try {
// return await request.post(url, body, headers);
// } catch (err) {
// console.error('下发设备命令失败:', {
// url,
// deviceId,
// serviceId,
// command,
// error: err.response?.data || err.message
// });
// throw err;
// }
// }
// module.exports = {
// getIamToken,
// queryDeviceMessages,
// sendDeviceCommand
// };
const request = require('./request.js');
// 华为云配置(需替换为实际值)
const config = {
iamEndpoint: 'https://iam.cn-south-1.myhuaweicloud.com',
iotEndpoint: 'https://iotda.cn-south-1.myhuaweicloud.com',
projectId: '8dc9c3dfebf14076adb6b8420fbb09a0',
credentials: {
username: 'KTD',
password: 'asd456852',
domain: {
name: 'hid_k2jbvvc8omizoea',
id: '1c6efe1c939a4665900226ba17663bbc'
}
},
// 新增:网络请求配置
network: {
timeout: 10000, // 请求超时时间(毫秒)
maxRetries: 3 // 最大重试次数
}
};
// 全局Token缓存
let tokenCache = {
token: null,
expiresAt: 0
};
/**
* 将华为云返回的ISO8601时间字符串转换为JS时间戳
*/
function parseIsoToTimestamp(isoStr) {
return new Date(isoStr).getTime();
}
/**
* 获取(或从缓存返回)IAM Token
*/
async function getIamToken() {
const now = Date.now();
// 提前2分钟刷新token,避免临界点问题
if (tokenCache.token && tokenCache.expiresAt > now + 2 * 60 * 1000) {
return tokenCache.token;
}
const url = `${config.iamEndpoint}/v3/auth/tokens`;
const authData = {
auth: {
identity: {
methods: ["password"],
password: {
user: {
name: config.credentials.username,
password: config.credentials.password,
domain: {
name: config.credentials.domain.name
}
}
}
},
scope: {
domain: {
id: config.credentials.domain.id
}
}
}
};
const headers = {
'Content-Type': 'application/json;charset=UTF-8',
'X-Domain-Id': config.credentials.domain.id,
'X-Language': 'zh-cn'
};
try {
const response = await request.post(url, authData, headers, true, {
timeout: config.network.timeout
});
const newToken = response.header['X-Subject-Token'];
if (!newToken) {
throw new Error("未从响应头中获取到X-Subject-Token");
}
const expiresAtIso = response.data?.token?.expires_at;
if (expiresAtIso) {
tokenCache.expiresAt = parseIsoToTimestamp(expiresAtIso);
} else {
console.warn("未获取到token过期时间,默认缓存1小时");
tokenCache.expiresAt = now + 3600 * 1000;
}
tokenCache.token = newToken;
console.log(`获取新Token成功,有效期至: ${new Date(tokenCache.expiresAt).toLocaleString()}`);
return newToken;
} catch (error) {
console.error('获取IAM Token失败:', error.message);
// 清空无效的token
tokenCache = { token: null, expiresAt: 0 };
throw error;
}
}
/**
* 带重试机制的HTTP请求
*/
async function requestWithRetry(requestFn, retries = config.network.maxRetries) {
let lastError;
for (let i = 0; i < retries; i++) {
try {
return await requestFn();
} catch (error) {
lastError = error;
console.warn(`请求失败,尝试第${i+1}/${retries}次重试:`, error.message);
// 指数退避策略
await new Promise(resolve => setTimeout(resolve, 500 * (i + 1)));
}
}
console.error(`达到最大重试次数(${retries}),请求最终失败`);
throw lastError;
}
/**
* 查询设备消息
*/
async function queryDeviceMessages(deviceId) {
const token = await getIamToken();
// 对设备ID进行编码,防止URL格式错误
const encodedDeviceId = encodeURIComponent(deviceId);
const url = `${config.iotEndpoint}/v5/iot/${config.projectId}/devices/${encodedDeviceId}/messages`;
return requestWithRetry(async () => {
return await request.get(url, {}, {
'Content-Type': 'application/json;charset=UTF-8',
'X-Auth-Token': token
}, {
timeout: config.network.timeout
});
});
}
/**
* 下发设备命令
*/
async function sendDeviceCommand(deviceId, serviceId, commandName, paras) {
const token = await getIamToken();
const encodedDeviceId = encodeURIComponent(deviceId);
const url = `${config.iotEndpoint}/v5/iot/${config.projectId}/devices/${encodedDeviceId}/commands`;
return requestWithRetry(async () => {
return await request.post(url, {
service_id: serviceId, // 使用传入的服务ID
command_name: commandName, // 使用动态命令名称
paras: paras // 参数必须为对象格式
}, {
'Content-Type': 'application/json;charset=UTF-8',
'X-Auth-Token': token
}, {
timeout: config.network.timeout
});
});
}
module.exports = {
getIamToken,
queryDeviceMessages,
sendDeviceCommand
};
这里面的token是全局的吗还是在单个文件中的
最新发布