对接飞奕API获取token

parent f8e817ba
......@@ -8,6 +8,14 @@ import { post } from '../util/request';
import { BizError } from '../util/bizError';
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 = {
* 生成随机字符串 nonce
* @param length 长度,默认 16
*/
function generateNonce(length: number = 16): string {
return crypto.randomBytes(length).toString('hex');
function generateNonce(): string {
return crypto.randomBytes(16).toString('hex');
}
/**
......@@ -36,7 +44,7 @@ function generateNonce(length: number = 16): string {
* @param params 请求参数对象
* @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 的参数(空字符串保留)
const filteredParams: Record<string, any> = {};
for (const key in params) {
......@@ -48,11 +56,11 @@ function generateSign(params: Record<string, any>, secretKey: string): string {
const sortedKeys = Object.keys(filteredParams).sort();
// 3. 拼接成 key1=value1&key2=value2...
const paramStr = sortedKeys.map(key => `${key}=${filteredParams[key]}`).join('&');
// 4. 拼接 SecretKey
const stringSignTemp = paramStr + secretKey;
// 5. MD5 并转大写
const md5 = crypto.createHash('md5').update(stringSignTemp).digest('hex');
return md5.toUpperCase();
// 4. 拼接timestamp、nonce和SecretKey
const stringSignTemp = paramStr + `&timestamp=${timestamp}&nonce=${nonce}&SecretKey=${secretKey}`;
console.log('MD5加密请求参数:', stringSignTemp);
// 6. MD5 并转大写
return crypto.createHash('md5').update(stringSignTemp).digest('hex').toUpperCase();
}
/**
......@@ -61,21 +69,29 @@ function generateSign(params: Record<string, any>, secretKey: string): string {
* @throws 如果请求失败或响应码非 00000,则抛出 BizError
*/
export async function getFeiYiToken(): Promise<string> {
const timestamp = Date.now().toString(); // 毫秒时间戳
const nonce = generateNonce();
// 校验token是否有效,有效则直接返回
const now = Date.now();
if (feiYiToken && expireTime > now) {
console.log(`使用缓存 token,剩余有效时间: ${(expireTime - now) / 1000} 秒`);
return feiYiToken;
}
// 根据文档,获取 token 的请求参数
const params = {
accessKey: '', // 文档要求传空串
clientId: FEIYI_CONFIG.clientId,
nonce: nonce,
timestamp: timestamp,
AccessKey: '', // 文档要求传空串
clientId: FEIYI_CONFIG.clientId
};
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 = {
...params,
sign: sign,
timestamp: timestamp,
nonce: nonce,
sign: sign
};
const url = `${FEIYI_CONFIG.baseUrl}/open/oauth2/openapi/token`;
......@@ -88,6 +104,7 @@ export async function getFeiYiToken(): Promise<string> {
} catch (err) {
throw new BizError(ERRORENUM.网络错误, `获取 token 网络失败: ${err.message}`);
}
console.log('获取 token 响应:', result);
// 检查响应结构:文档中返回 { code, data: { access_token } }
if (!result || result.code !== '00000') {
......@@ -97,19 +114,28 @@ export async function getFeiYiToken(): Promise<string> {
const accessToken = result.data?.access_token;
if (!accessToken) {
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;
}
/**
* 带 Token 的 POST 请求封装
* 自动获取/刷新 token(简单实现,生产环境建议做 token 缓存和过期刷新)
* 自动获取/刷新 token
* @param path 接口路径(相对路径)
* @param body 请求体
* @param retry 是否重试(用于 token 过期重试)
*/
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 headers = {
'Authorization': `Bearer ${token}`,
......@@ -119,6 +145,7 @@ async function feiYiPost(path: string, body: any, retry: boolean = true): Promis
let result: any;
try {
result = await post(url, body, headers);
console.log(`请求 ${path} 成功,响应:`, result);
} catch (err) {
throw new BizError(ERRORENUM.网络错误, `请求 ${path} 失败: ${err.message}`);
}
......
......@@ -5,39 +5,36 @@
import asyncHandler from 'express-async-handler';
import * as deviceBiz from '../biz/device';
import * as regionBiz from '../biz/region';
import * as runningBiz from '../biz/running';
import { eccReqParamater } from '../util/verificationParam';
import { checkUser } from '../middleware/user';
export function setRouter(httpServer) {
/** 注册/更新设备信息 */
httpServer.post('/api/zc/device/register', asyncHandler(registerDevice));
httpServer.post('/api/zc/region/regionList', asyncHandler(getRegionList));
httpServer.post('/api/qdm/device/register', asyncHandler(registerDevice));
/** 设备商推送实时数据 */
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/qdm/region/regionList', asyncHandler(getRegionList));
/** 运行分析 */
httpServer.post('/api/zc/run/analysis', asyncHandler(getRunAnalysis));
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/zc/run/analysis/pop', asyncHandler(getRunAnalysisPop));
httpServer.post('/api/qdm/run/monitor/acRun', asyncHandler(controlAcRunning));
/** 智能监控弹窗 */
httpServer.post('/api/zc/smart/monitor/pop', asyncHandler(getSmartMonitorPop));
}
/**
* 注册或更新设备信息
*/
......@@ -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) {
......@@ -75,7 +58,6 @@ async function deviceDataPush(req, res) {
res.success(result);
}
/**
* 设备商推送故障数据
*/
......@@ -84,17 +66,29 @@ async function deviceFaultPush(req, res) {
const NotMustHaveKeys = ["faultDescription"];
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);
}
/**
* 获取区域列表接口,供前端下拉选择区域类型和区域名称
* @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) {
const { } = req.body;
const result = await regionBiz.getRunAnalysis("","");
const result = await runningBiz.getRunAnalysis("","");
res.success(result);
}
......@@ -105,7 +99,7 @@ async function deviceFaultPush(req, res) {
let reqConf = {regionGroup:'String'};
const NotMustHaveKeys = [];
let { regionGroup } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await regionBiz.getRunAnalysis("", regionGroup);
const result = await runningBiz.getRunAnalysis(regionGroup, "");
res.success(result);
}
......@@ -116,7 +110,7 @@ async function deviceFaultPush(req, res) {
let reqConf = {regionGroup:'String', regionType:'String'};
const 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);
}
......@@ -127,7 +121,7 @@ async function deviceFaultPush(req, res) {
let reqConf = {regionKey:'Number', deviceId:'String'};
const 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);
}
......@@ -138,32 +132,10 @@ async function controlAcRunning(req, res) {
let reqConf = {deviceId:'String'};
const 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);
}
/**
* 智能监控弹窗
*/
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 * as feiyiClientBiz from '../biz/feiyiClient';
import { eccReqParamater } from '../util/verificationParam';
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';
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