对接飞奕API获取token

parent f8e817ba
...@@ -8,6 +8,14 @@ import { post } from '../util/request'; ...@@ -8,6 +8,14 @@ import { post } from '../util/request';
import { BizError } from '../util/bizError'; import { BizError } from '../util/bizError';
import { ERRORENUM } from '../config/errorEnum'; import { ERRORENUM } from '../config/errorEnum';
// 全局变量缓存 token 和过期时间
let feiYiToken = "";
let expireTime = 0;
let getTokenTime = 0;
let isFetchingToken = false; // 避免并发获取 token 的标志位
let refreshTokenPromise: Promise<string> | null = null; // 用于避免并发刷新 token 的 Promise
/** /**
* 飞奕云平台配置(建议从环境变量或配置文件中读取) * 飞奕云平台配置(建议从环境变量或配置文件中读取)
*/ */
...@@ -21,8 +29,8 @@ const FEIYI_CONFIG = { ...@@ -21,8 +29,8 @@ const FEIYI_CONFIG = {
* 生成随机字符串 nonce * 生成随机字符串 nonce
* @param length 长度,默认 16 * @param length 长度,默认 16
*/ */
function generateNonce(length: number = 16): string { function generateNonce(): string {
return crypto.randomBytes(length).toString('hex'); return crypto.randomBytes(16).toString('hex');
} }
/** /**
...@@ -36,7 +44,7 @@ function generateNonce(length: number = 16): string { ...@@ -36,7 +44,7 @@ function generateNonce(length: number = 16): string {
* @param params 请求参数对象 * @param params 请求参数对象
* @param secretKey 密钥 * @param secretKey 密钥
*/ */
function generateSign(params: Record<string, any>, secretKey: string): string { function generateSign(params: Record<string, any>, timestamp: string, nonce: string, secretKey: string, charset: string = 'UTF-8'): string {
// 1. 过滤掉值为 null 或 undefined 的参数(空字符串保留) // 1. 过滤掉值为 null 或 undefined 的参数(空字符串保留)
const filteredParams: Record<string, any> = {}; const filteredParams: Record<string, any> = {};
for (const key in params) { for (const key in params) {
...@@ -48,11 +56,11 @@ function generateSign(params: Record<string, any>, secretKey: string): string { ...@@ -48,11 +56,11 @@ function generateSign(params: Record<string, any>, secretKey: string): string {
const sortedKeys = Object.keys(filteredParams).sort(); const sortedKeys = Object.keys(filteredParams).sort();
// 3. 拼接成 key1=value1&key2=value2... // 3. 拼接成 key1=value1&key2=value2...
const paramStr = sortedKeys.map(key => `${key}=${filteredParams[key]}`).join('&'); const paramStr = sortedKeys.map(key => `${key}=${filteredParams[key]}`).join('&');
// 4. 拼接 SecretKey // 4. 拼接timestamp、nonce和SecretKey
const stringSignTemp = paramStr + secretKey; const stringSignTemp = paramStr + `&timestamp=${timestamp}&nonce=${nonce}&SecretKey=${secretKey}`;
// 5. MD5 并转大写 console.log('MD5加密请求参数:', stringSignTemp);
const md5 = crypto.createHash('md5').update(stringSignTemp).digest('hex'); // 6. MD5 并转大写
return md5.toUpperCase(); return crypto.createHash('md5').update(stringSignTemp).digest('hex').toUpperCase();
} }
/** /**
...@@ -61,21 +69,29 @@ function generateSign(params: Record<string, any>, secretKey: string): string { ...@@ -61,21 +69,29 @@ function generateSign(params: Record<string, any>, secretKey: string): string {
* @throws 如果请求失败或响应码非 00000,则抛出 BizError * @throws 如果请求失败或响应码非 00000,则抛出 BizError
*/ */
export async function getFeiYiToken(): Promise<string> { export async function getFeiYiToken(): Promise<string> {
const timestamp = Date.now().toString(); // 毫秒时间戳 // 校验token是否有效,有效则直接返回
const nonce = generateNonce(); const now = Date.now();
if (feiYiToken && expireTime > now) {
console.log(`使用缓存 token,剩余有效时间: ${(expireTime - now) / 1000} 秒`);
return feiYiToken;
}
// 根据文档,获取 token 的请求参数
const params = { const params = {
accessKey: '', // 文档要求传空串 AccessKey: '', // 文档要求传空串
clientId: FEIYI_CONFIG.clientId, clientId: FEIYI_CONFIG.clientId
nonce: nonce,
timestamp: timestamp,
}; };
// 生成签名
const sign = generateSign(params, FEIYI_CONFIG.secretKey); const timestamp = Date.now().toString(); // 毫秒时间戳
const nonce = generateNonce();
const sign = generateSign(params, timestamp, nonce, FEIYI_CONFIG.secretKey);
console.log('MD5加密签名:', sign);
const requestBody = { const requestBody = {
...params, ...params,
sign: sign, timestamp: timestamp,
nonce: nonce,
sign: sign
}; };
const url = `${FEIYI_CONFIG.baseUrl}/open/oauth2/openapi/token`; const url = `${FEIYI_CONFIG.baseUrl}/open/oauth2/openapi/token`;
...@@ -88,6 +104,7 @@ export async function getFeiYiToken(): Promise<string> { ...@@ -88,6 +104,7 @@ export async function getFeiYiToken(): Promise<string> {
} catch (err) { } catch (err) {
throw new BizError(ERRORENUM.网络错误, `获取 token 网络失败: ${err.message}`); throw new BizError(ERRORENUM.网络错误, `获取 token 网络失败: ${err.message}`);
} }
console.log('获取 token 响应:', result);
// 检查响应结构:文档中返回 { code, data: { access_token } } // 检查响应结构:文档中返回 { code, data: { access_token } }
if (!result || result.code !== '00000') { if (!result || result.code !== '00000') {
...@@ -97,19 +114,28 @@ export async function getFeiYiToken(): Promise<string> { ...@@ -97,19 +114,28 @@ export async function getFeiYiToken(): Promise<string> {
const accessToken = result.data?.access_token; const accessToken = result.data?.access_token;
if (!accessToken) { if (!accessToken) {
throw new BizError(ERRORENUM.第三方接口错误, '响应中未包含 access_token'); throw new BizError(ERRORENUM.第三方接口错误, '响应中未包含 access_token');
} else {
// 更新全局 token 和过期时间
feiYiToken = accessToken;
getTokenTime = now;
// 默认 2分钟 过期,如果响应中包含 expires_in 字段,则使用该值(单位毫秒)
expireTime = now + (result.data.expires_in ? result.data.expires_in : 2 * 60 * 1000);
console.log(`获取新 token: ${accessToken}, 过期时间: ${new Date(expireTime).toISOString()}`);
} }
return accessToken; return accessToken;
} }
/** /**
* 带 Token 的 POST 请求封装 * 带 Token 的 POST 请求封装
* 自动获取/刷新 token(简单实现,生产环境建议做 token 缓存和过期刷新) * 自动获取/刷新 token
* @param path 接口路径(相对路径) * @param path 接口路径(相对路径)
* @param body 请求体 * @param body 请求体
* @param retry 是否重试(用于 token 过期重试) * @param retry 是否重试(用于 token 过期重试)
*/ */
async function feiYiPost(path: string, body: any, retry: boolean = true): Promise<any> { async function feiYiPost(path: string, body: any, retry: boolean = true): Promise<any> {
const token = await getFeiYiToken(); // 每次都获取最新 token(简单起见,可缓存) // 获取 token
const token = await getFeiYiToken();
const url = `${FEIYI_CONFIG.baseUrl}${path}`; const url = `${FEIYI_CONFIG.baseUrl}${path}`;
const headers = { const headers = {
'Authorization': `Bearer ${token}`, 'Authorization': `Bearer ${token}`,
...@@ -119,6 +145,7 @@ async function feiYiPost(path: string, body: any, retry: boolean = true): Promis ...@@ -119,6 +145,7 @@ async function feiYiPost(path: string, body: any, retry: boolean = true): Promis
let result: any; let result: any;
try { try {
result = await post(url, body, headers); result = await post(url, body, headers);
console.log(`请求 ${path} 成功,响应:`, result);
} catch (err) { } catch (err) {
throw new BizError(ERRORENUM.网络错误, `请求 ${path} 失败: ${err.message}`); throw new BizError(ERRORENUM.网络错误, `请求 ${path} 失败: ${err.message}`);
} }
......
...@@ -5,39 +5,36 @@ ...@@ -5,39 +5,36 @@
import asyncHandler from 'express-async-handler'; import asyncHandler from 'express-async-handler';
import * as deviceBiz from '../biz/device'; import * as deviceBiz from '../biz/device';
import * as regionBiz from '../biz/region'; import * as regionBiz from '../biz/region';
import * as runningBiz from '../biz/running';
import { eccReqParamater } from '../util/verificationParam'; import { eccReqParamater } from '../util/verificationParam';
import { checkUser } from '../middleware/user'; import { checkUser } from '../middleware/user';
export function setRouter(httpServer) { export function setRouter(httpServer) {
/** 注册/更新设备信息 */ /** 注册/更新设备信息 */
httpServer.post('/api/zc/device/register', asyncHandler(registerDevice)); httpServer.post('/api/qdm/device/register', asyncHandler(registerDevice));
httpServer.post('/api/zc/region/regionList', asyncHandler(getRegionList));
/** 设备商推送实时数据 */ /** 设备商推送实时数据 */
httpServer.post('/api/zc/device/push', asyncHandler(deviceDataPush)); httpServer.post('/api/qdm/device/push', asyncHandler(deviceDataPush));
/** 设备商推送故障数据 */ /** 设备商推送故障数据 */
httpServer.post('/api/zc/device/fault/push', asyncHandler(deviceFaultPush)); httpServer.post('/api/qdm/device/fault/push', asyncHandler(deviceFaultPush));
/** 运行分析 */ /** 获取区域列表 */
httpServer.post('/api/zc/run/analysis', asyncHandler(getRunAnalysis)); httpServer.post('/api/qdm/region/regionList', asyncHandler(getRegionList));
/** 运行分析 */
httpServer.post('/api/qdm/run/analysis', asyncHandler(getRunAnalysis));
/** 运行分析 - 楼栋 */ /** 运行分析 - 楼栋 */
httpServer.post('/api/zc/run/analysis/building', asyncHandler(getRunAnalysisByBuilding)); httpServer.post('/api/qdm/run/analysis/building', asyncHandler(getRunAnalysisByBuilding));
/** 运行分析 - 楼层 */ /** 运行分析 - 楼层 */
httpServer.post('/api/zc/run/analysis/Floor', asyncHandler(getRunAnalysisByFloor)); httpServer.post('/api/qdm/run/analysis/Floor', asyncHandler(getRunAnalysisByFloor));
/** 空调运行弹框 */ /** 空调运行弹框 */
httpServer.post('/api/zc/run/monitor/acPop', asyncHandler(getAcRunningPop)); httpServer.post('/api/qdm/run/monitor/acPop', asyncHandler(getAcRunningPop));
/** 空调开关 */ /** 空调开关 */
httpServer.post('/api/zc/run/monitor/acRun', asyncHandler(controlAcRunning)); httpServer.post('/api/qdm/run/monitor/acRun', asyncHandler(controlAcRunning));
/**运行分析-弹窗 */
httpServer.post('/api/zc/run/analysis/pop', asyncHandler(getRunAnalysisPop));
/** 智能监控弹窗 */
httpServer.post('/api/zc/smart/monitor/pop', asyncHandler(getSmartMonitorPop));
} }
/** /**
* 注册或更新设备信息 * 注册或更新设备信息
*/ */
...@@ -50,20 +47,6 @@ export function setRouter(httpServer) { ...@@ -50,20 +47,6 @@ export function setRouter(httpServer) {
} }
/** /**
* 获取区域列表接口,供前端下拉选择区域类型和区域名称
* @param req
* @param res
*/
async function getRegionList(req, res) {
let reqConf = {regionGroup:'String'};
const NotMustHaveKeys = [];
let { regionGroup } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await regionBiz.getRegionList(regionGroup);
res.success(result);
}
/**
* 设备商推送数据 * 设备商推送数据
*/ */
async function deviceDataPush(req, res) { async function deviceDataPush(req, res) {
...@@ -75,7 +58,6 @@ async function deviceDataPush(req, res) { ...@@ -75,7 +58,6 @@ async function deviceDataPush(req, res) {
res.success(result); res.success(result);
} }
/** /**
* 设备商推送故障数据 * 设备商推送故障数据
*/ */
...@@ -84,17 +66,29 @@ async function deviceFaultPush(req, res) { ...@@ -84,17 +66,29 @@ async function deviceFaultPush(req, res) {
const NotMustHaveKeys = ["faultDescription"]; const NotMustHaveKeys = ["faultDescription"];
let { deviceId, faultType, faultLevel, faultDescription, occurredTime } = eccReqParamater(reqConf, req.body, NotMustHaveKeys); let { deviceId, faultType, faultLevel, faultDescription, occurredTime } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await regionBiz.handleDeviceFaultPush(deviceId, faultType, faultLevel, faultDescription, occurredTime); const result = await deviceBiz.handleDeviceFaultPush(deviceId, faultType, faultLevel, faultDescription, occurredTime);
res.success(result); res.success(result);
} }
/**
* 获取区域列表接口,供前端下拉选择区域类型和区域名称
* @param req
* @param res
*/
async function getRegionList(req, res) {
let reqConf = {regionGroup:'String'};
const NotMustHaveKeys = [];
let { regionGroup } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await regionBiz.getRegionList(regionGroup);
res.success(result);
}
/** /**
* 运行分析 * 运行分析
*/ */
async function getRunAnalysis(req, res) { async function getRunAnalysis(req, res) {
const { } = req.body; const { } = req.body;
const result = await regionBiz.getRunAnalysis("",""); const result = await runningBiz.getRunAnalysis("","");
res.success(result); res.success(result);
} }
...@@ -105,7 +99,7 @@ async function deviceFaultPush(req, res) { ...@@ -105,7 +99,7 @@ async function deviceFaultPush(req, res) {
let reqConf = {regionGroup:'String'}; let reqConf = {regionGroup:'String'};
const NotMustHaveKeys = []; const NotMustHaveKeys = [];
let { regionGroup } = eccReqParamater(reqConf, req.body, NotMustHaveKeys); let { regionGroup } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await regionBiz.getRunAnalysis("", regionGroup); const result = await runningBiz.getRunAnalysis(regionGroup, "");
res.success(result); res.success(result);
} }
...@@ -116,7 +110,7 @@ async function deviceFaultPush(req, res) { ...@@ -116,7 +110,7 @@ async function deviceFaultPush(req, res) {
let reqConf = {regionGroup:'String', regionType:'String'}; let reqConf = {regionGroup:'String', regionType:'String'};
const NotMustHaveKeys = []; const NotMustHaveKeys = [];
let { regionGroup, regionType } = eccReqParamater(reqConf, req.body, NotMustHaveKeys); let { regionGroup, regionType } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await regionBiz.getRunAnalysis(regionType, regionGroup); const result = await runningBiz.getRunAnalysis(regionGroup, regionType);
res.success(result); res.success(result);
} }
...@@ -127,7 +121,7 @@ async function deviceFaultPush(req, res) { ...@@ -127,7 +121,7 @@ async function deviceFaultPush(req, res) {
let reqConf = {regionKey:'Number', deviceId:'String'}; let reqConf = {regionKey:'Number', deviceId:'String'};
const NotMustHaveKeys = []; const NotMustHaveKeys = [];
let { regionKey, deviceId } = eccReqParamater(reqConf, req.body, NotMustHaveKeys); let { regionKey, deviceId } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await regionBiz.getAcRunningPop(regionKey, deviceId); const result = await runningBiz.getAcRunningPop(regionKey, deviceId);
res.success(result); res.success(result);
} }
...@@ -138,33 +132,11 @@ async function controlAcRunning(req, res) { ...@@ -138,33 +132,11 @@ async function controlAcRunning(req, res) {
let reqConf = {deviceId:'String'}; let reqConf = {deviceId:'String'};
const NotMustHaveKeys = []; const NotMustHaveKeys = [];
let { deviceId } = eccReqParamater(reqConf, req.body, NotMustHaveKeys); let { deviceId } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await regionBiz.controlAcRunning(deviceId); const result = await runningBiz.controlAcRunning(deviceId);
res.success(result);
}
/**
* 运行分析-弹窗
*/
async function getRunAnalysisPop(req, res) {
let reqConf = {regionKey:'Number'};
const NotMustHaveKeys = [];
let { regionKey } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await deviceBiz.getRunAnalysisPop(regionKey);
res.success(result); res.success(result);
} }
/**
* 智能监控弹窗
*/
async function getSmartMonitorPop(req, res) {
let reqConf = {regionKey:'Number'};
const NotMustHaveKeys = [];
let { regionKey } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await deviceBiz.getSmartMonitorPop(regionKey);
res.success(result);
}
......
/** /**
* * 访问飞翼客户端API接口
*/ */
import asyncHandler from 'express-async-handler'; import asyncHandler from 'express-async-handler';
import * as feiyiClientBiz from '../biz/feiyiClient'; import * as feiyiClientBiz from '../biz/feiyiClient';
import { eccReqParamater } from '../util/verificationParam'; import { eccReqParamater } from '../util/verificationParam';
export function setRouter(httpServer) { export function setRouter(httpServer) {
/** 注册/更新设备信息 */ /** 注册/更新设备信息 */
httpServer.post('/api/zc/feiyi/gateway/list', asyncHandler(getFeiyiGatewayList)); httpServer.post('/api/qdm/feiyi/gateway/list', asyncHandler(getFeiyiGatewayList));
} }
......
...@@ -7,10 +7,10 @@ import { eccReqParamater } from '../util/verificationParam'; ...@@ -7,10 +7,10 @@ import { eccReqParamater } from '../util/verificationParam';
export function setRouter(httpServer) { export function setRouter(httpServer) {
/** 用户注册 */ /** 用户注册 */
httpServer.post('/api/zc/user/register', asyncHandler(registerUser)); httpServer.post('/api/qdm/user/register', asyncHandler(registerUser));
/** 用户登录 */ /** 用户登录 */
httpServer.post('/api/zc/user/login', asyncHandler(login)); httpServer.post('/api/qdm/user/login', asyncHandler(login));
/** /**
* 注册信息 * 注册信息
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment