674 lines
19 KiB
JavaScript
674 lines
19 KiB
JavaScript
|
|
const envFile = `.env.${process.env.NODE_ENV || "development"}`;
|
|||
|
|
require("dotenv").config({ path: envFile });
|
|||
|
|
|
|||
|
|
const express = require("express");
|
|||
|
|
const cors = require("cors");
|
|||
|
|
const timeout = require("connect-timeout");
|
|||
|
|
const mongodb = require("./mongodb");
|
|||
|
|
const upload = require("./upload");
|
|||
|
|
const bodyParser = require("body-parser");
|
|||
|
|
const corsUtils = require("./utils/cors");
|
|||
|
|
const logger = require("./utils/logger");
|
|||
|
|
const trade = require("./hlw/trade");
|
|||
|
|
const fengniao = require("./hlw/fengniao");
|
|||
|
|
|
|||
|
|
const {
|
|||
|
|
safeSend,
|
|||
|
|
sendSuccess,
|
|||
|
|
sendError,
|
|||
|
|
asyncHandler,
|
|||
|
|
} = require("./utils/response");
|
|||
|
|
const {
|
|||
|
|
serverConfig,
|
|||
|
|
optimizeServer,
|
|||
|
|
setupProcessHandlers,
|
|||
|
|
} = require("./utils/server-config");
|
|||
|
|
const {
|
|||
|
|
monitoringMiddleware,
|
|||
|
|
errorMonitoringMiddleware,
|
|||
|
|
monitor,
|
|||
|
|
} = require("./utils/performance-monitor");
|
|||
|
|
const { checkDatabaseHealth, safeGetDatabase } = require("./utils/db-health");
|
|||
|
|
const member = require("./member");
|
|||
|
|
const todo = require("./toDoEvents");
|
|||
|
|
const groupmsg = require("./groupmsg");
|
|||
|
|
const corp = require("./corp");
|
|||
|
|
const knowledgeBase = require("./knowledgeBase");
|
|||
|
|
const survery = require("./survery");
|
|||
|
|
const weCom = require("./weCom");
|
|||
|
|
const system = require("./system");
|
|||
|
|
const sessionArchive = require("./sessionArchive");
|
|||
|
|
const customerHisSync = require("./customerHisSync");
|
|||
|
|
const hlw = require("./hlw");
|
|||
|
|
const trigger = require("./trigger");
|
|||
|
|
const consultTrigger = require("./hlw/consult-order/trigger");
|
|||
|
|
const callBack = require("./callBack");
|
|||
|
|
const alipayApi = require("./alipayApi");
|
|||
|
|
const auth = require("./auth");
|
|||
|
|
const { jwtAuth, optionalJwtAuth } = require("./middleware/jwt-auth");
|
|||
|
|
const { smartAuth } = require("./middleware/smart-auth");
|
|||
|
|
const sms = require("./sms");
|
|||
|
|
const path = require("path");
|
|||
|
|
const { useUploadFileParse } = require("./upload-file-parse");
|
|||
|
|
const app = express();
|
|||
|
|
const PORT = process.env.CONFIG_NODE_PORT;
|
|||
|
|
const { getDatabase, connectToMongoDB } = require("./mongodb");
|
|||
|
|
|
|||
|
|
// 禁用 X-Powered-By 响应头,防止版本信息泄露
|
|||
|
|
app.disable("x-powered-by");
|
|||
|
|
|
|||
|
|
// 设置进程异常处理
|
|||
|
|
setupProcessHandlers();
|
|||
|
|
|
|||
|
|
// 设置CORS中间件,允许所有来源的请求
|
|||
|
|
app.use(cors(corsUtils.cors));
|
|||
|
|
|
|||
|
|
// 安全响应头中间件,防止版本信息泄露
|
|||
|
|
app.use((req, res, next) => {
|
|||
|
|
// 移除可能泄露版本信息的响应头
|
|||
|
|
res.removeHeader("X-Powered-By");
|
|||
|
|
res.removeHeader("Server");
|
|||
|
|
// 添加安全响应头
|
|||
|
|
res.setHeader("X-Content-Type-Options", "nosniff");
|
|||
|
|
res.setHeader("X-Frame-Options", "DENY");
|
|||
|
|
res.setHeader("X-XSS-Protection", "1; mode=block");
|
|||
|
|
next();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 添加性能监控中间件
|
|||
|
|
app.use(monitoringMiddleware);
|
|||
|
|
|
|||
|
|
// 启动定期性能报告
|
|||
|
|
monitor.startPeriodicReport(300000); // 每5分钟报告一次
|
|||
|
|
|
|||
|
|
app.use(timeout(serverConfig.timeout)); // 使用配置的超时时间
|
|||
|
|
|
|||
|
|
// 添加超时处理中间件 - 必须在所有路由之前
|
|||
|
|
app.use((req, res, next) => {
|
|||
|
|
// 如果请求没有超时,继续处理
|
|||
|
|
if (!req.timedout) {
|
|||
|
|
next();
|
|||
|
|
} else {
|
|||
|
|
// 如果已经超时,直接返回超时错误
|
|||
|
|
sendError(res, "请求超时", "Request timeout", 408);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 设置请求头中间件,允许自定义请求头(如果需要的话)
|
|||
|
|
app.use((req, res, next) => {
|
|||
|
|
res.header(
|
|||
|
|
"Access-Control-Allow-Headers",
|
|||
|
|
"Content-Type, Authorization, X-Custom-Header"
|
|||
|
|
);
|
|||
|
|
next();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 解析JSON格式的请求体 - 使用配置的大小限制
|
|||
|
|
app.use(bodyParser.json({ limit: serverConfig.bodyLimit }));
|
|||
|
|
// 解析urlencoded格式的请求体
|
|||
|
|
app.use(
|
|||
|
|
bodyParser.urlencoded({ extended: true, limit: serverConfig.bodyLimit })
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.use(bodyParser.text({ type: "*/xml", limit: serverConfig.bodyLimit }));
|
|||
|
|
|
|||
|
|
// GET请求处理器
|
|||
|
|
app.get("/", (req, res) => {
|
|||
|
|
sendSuccess(res, null, "GET request received");
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// ==================== 认证相关接口(无需 JWT 验证) ====================
|
|||
|
|
|
|||
|
|
// 用户登录接口
|
|||
|
|
app.post(
|
|||
|
|
"/auth/login",
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = { ...req.body, ip: req.ip };
|
|||
|
|
const db = await getDatabase("corp");
|
|||
|
|
const result = await auth.main({ ...requestData, type: "login" }, db);
|
|||
|
|
safeSend(res, result);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 小程序授权登录接口
|
|||
|
|
app.post(
|
|||
|
|
"/auth/miniapp/login",
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = { ...req.body, ip: req.ip };
|
|||
|
|
const db = await getDatabase("corp");
|
|||
|
|
const result = await auth.main(
|
|||
|
|
{ ...requestData, type: "miniProgramLogin" },
|
|||
|
|
db
|
|||
|
|
);
|
|||
|
|
safeSend(res, result);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// Token 刷新接口
|
|||
|
|
app.post(
|
|||
|
|
"/auth/refresh",
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = req.body;
|
|||
|
|
const result = await auth.main({ ...requestData, type: "refreshToken" });
|
|||
|
|
safeSend(res, result);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/getCloudServiceData",
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = await getDatabase("Internet-hospital");
|
|||
|
|
const item = await hlw({ type: "getLastestPurchaseOrder", ciphertext: req.body.data }, db);
|
|||
|
|
res.send(item);
|
|||
|
|
} catch (error) {
|
|||
|
|
res.send({ success: false, message: `获取最新购药订单错误: ${error.message}` });
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 获取当前用户信息(需要 JWT 验证)
|
|||
|
|
app.get(
|
|||
|
|
"/auth/current",
|
|||
|
|
jwtAuth,
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const db = await getDatabase("corp");
|
|||
|
|
const result = await auth.main({ type: "getCurrentUser" }, db, req.user);
|
|||
|
|
safeSend(res, result);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 用户退出登录(需要 JWT 验证)
|
|||
|
|
app.post(
|
|||
|
|
"/auth/logout",
|
|||
|
|
jwtAuth,
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const db = await getDatabase("corp");
|
|||
|
|
const result = await auth.main({ type: "logout" }, db, req.user);
|
|||
|
|
safeSend(res, result);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// ==================== 以下接口需要 JWT 验证 ====================
|
|||
|
|
|
|||
|
|
// 连接数据库
|
|||
|
|
connectToMongoDB().catch((err) => {
|
|||
|
|
logger.error("数据库连接失败,服务器将无法正常工作:", err);
|
|||
|
|
// 不退出进程,让错误处理中间件处理后续请求
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
app.get(
|
|||
|
|
"/zyt/stats",
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = await getDatabase("Internet-hospital");
|
|||
|
|
const item = await hlw(
|
|||
|
|
{ ...req.query, type: "hlwStats", statsType: req.query.type },
|
|||
|
|
db
|
|||
|
|
);
|
|||
|
|
safeSend(res, item);
|
|||
|
|
} catch (error) {
|
|||
|
|
sendError(res, "数据库连接失败", error.message, 500);
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.get(
|
|||
|
|
"/zyt/2excel",
|
|||
|
|
// jwtAuth,
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const requestData = req.query;
|
|||
|
|
const db = await getDatabase("Internet-hospital");
|
|||
|
|
const message = await hlw(
|
|||
|
|
{ ...req.query, type: "downloadXlsx", downloadType: req.query.type },
|
|||
|
|
db,
|
|||
|
|
res
|
|||
|
|
);
|
|||
|
|
if (message) {
|
|||
|
|
safeSend(res, message);
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
sendError(res, "数据库连接失败", error.message, 500);
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// POST请求处理器
|
|||
|
|
app.post(
|
|||
|
|
"/getYoucanData/member",
|
|||
|
|
jwtAuth,
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = req.body;
|
|||
|
|
try {
|
|||
|
|
const db = await getDatabase("admin");
|
|||
|
|
let item = await member.main(requestData, db);
|
|||
|
|
safeSend(res, item);
|
|||
|
|
} catch (error) {
|
|||
|
|
sendError(res, "数据库连接失败", error.message, 500);
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/getYoucanData/todo",
|
|||
|
|
jwtAuth,
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = req.body;
|
|||
|
|
const db = await getDatabase("admin");
|
|||
|
|
let item = await todo.main(requestData, db);
|
|||
|
|
safeSend(res, item);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/getYoucanData/groupmsg",
|
|||
|
|
jwtAuth,
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = req.body;
|
|||
|
|
const db = await getDatabase("admin");
|
|||
|
|
let item = await groupmsg.main(requestData, db);
|
|||
|
|
safeSend(res, item);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/getYoucanData/corp",
|
|||
|
|
smartAuth, // 使用智能鉴权,支持白名单 type 跳过鉴权
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = req.body;
|
|||
|
|
const db = await getDatabase("corp");
|
|||
|
|
const item = await corp.main(requestData, db, req); // 传递 user 信息
|
|||
|
|
logger.info(`接口请求:${JSON.stringify(requestData)}`);
|
|||
|
|
logger.info(`接口输出:${JSON.stringify(item)}`);
|
|||
|
|
safeSend(res, item);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/getYoucanData/knowledgeBase",
|
|||
|
|
smartAuth, // 使用智能鉴权,支持白名单 type 跳过鉴权(如文章相关接口)
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = req.body;
|
|||
|
|
const db = await getDatabase("corp");
|
|||
|
|
const item = await knowledgeBase.main(requestData, db, req.user); // 传递 user 信息以便需要时使用
|
|||
|
|
safeSend(res, item);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/getYoucanData/survery",
|
|||
|
|
jwtAuth,
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = req.body;
|
|||
|
|
const db = await getDatabase("corp");
|
|||
|
|
const item = await survery.main(requestData, db);
|
|||
|
|
safeSend(res, item);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/getYoucanData/system",
|
|||
|
|
smartAuth, // 使用智能鉴权,支持白名单 type 跳过鉴权
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = req.body;
|
|||
|
|
const db = await getDatabase("corp");
|
|||
|
|
const item = await system.main(requestData, db, req.user); // 传递 user 信息
|
|||
|
|
safeSend(res, item);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/getYoucanData/sessionArchive",
|
|||
|
|
jwtAuth,
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = req.body;
|
|||
|
|
const db = await getDatabase("corp");
|
|||
|
|
const item = await sessionArchive.main(requestData, db);
|
|||
|
|
safeSend(res, item);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/getYoucanData/weCom",
|
|||
|
|
jwtAuth,
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = req.body;
|
|||
|
|
const item = await weCom.main(requestData);
|
|||
|
|
safeSend(res, item);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/getYoucanData/customerHisSync",
|
|||
|
|
jwtAuth,
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = req.body;
|
|||
|
|
const item = await customerHisSync.main(requestData);
|
|||
|
|
safeSend(res, item);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/getYoucanData/sms",
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = req.body;
|
|||
|
|
const item = await sms.main(requestData);
|
|||
|
|
safeSend(res, item);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 添加静态HTML文件访问路径
|
|||
|
|
app.use("/", express.static(path.join(__dirname, "./static")));
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/getAlipayData",
|
|||
|
|
smartAuth, // 使用智能鉴权,支持白名单 type 跳过鉴权
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = req.body;
|
|||
|
|
const item = await alipayApi.main(requestData);
|
|||
|
|
safeSend(res, item);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/getYoucanData/hlw",
|
|||
|
|
smartAuth, // 使用智能鉴权,支持白名单 type 跳过鉴权
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = req.body;
|
|||
|
|
const db = await getDatabase("Internet-hospital");
|
|||
|
|
const item = await hlw(requestData, db, req.user); // 传递 user 信息
|
|||
|
|
safeSend(res, item);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/alipay-pay-notify",
|
|||
|
|
smartAuth, // 使用智能鉴权,支持白名单 type 跳过鉴权
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
await trade.handlePayNotify(req, res);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/union-pay-notify",
|
|||
|
|
smartAuth, // 使用智能鉴权,支持白名单 type 跳过鉴权
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
await trade.handleUnionPayNotify(req, res);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/fengniao-auth",
|
|||
|
|
smartAuth, // 使用智能鉴权,支持白名单 type 跳过鉴权
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
await fengniao.handleFengNiaoAuth(req, res);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/fengniao-notify",
|
|||
|
|
smartAuth, // 使用智能鉴权,支持白名单 type 跳过鉴权
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
await fengniao.notify(req, res);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/zyt-his-notify",
|
|||
|
|
smartAuth, // 使用智能鉴权,支持白名单 type 跳过鉴权
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const notifyHandler = require('./hlw/zyt-his/notify');
|
|||
|
|
const requestData = req.body;
|
|||
|
|
const result = await notifyHandler.handleNotify(requestData);
|
|||
|
|
safeSend(res, result)
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/IMCallBack",
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = req.body;
|
|||
|
|
const db = await getDatabase("Internet-hospital");
|
|||
|
|
await hlw(
|
|||
|
|
{
|
|||
|
|
type: "addChatMsg",
|
|||
|
|
params: requestData,
|
|||
|
|
},
|
|||
|
|
db
|
|||
|
|
);
|
|||
|
|
sendSuccess(res, null, "请求成功");
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* mPaaS 音视频通话:云端自动录制回调
|
|||
|
|
* - 控制台配置:通话应用 -> 功能配置 -> 配置云端自动录制及录制回调地址
|
|||
|
|
* - 回调方式:POST(application/json)
|
|||
|
|
* - 业务侧响应:{ bizRequestId, code: 0 }
|
|||
|
|
*/
|
|||
|
|
app.post(
|
|||
|
|
"/mrtc/record/callback",
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const payload = req.body && typeof req.body === "object" ? req.body : {};
|
|||
|
|
const {
|
|||
|
|
bizRequestId,
|
|||
|
|
bizName,
|
|||
|
|
appId,
|
|||
|
|
workspaceId,
|
|||
|
|
roomId,
|
|||
|
|
recordId,
|
|||
|
|
eventCode,
|
|||
|
|
recordResult,
|
|||
|
|
} = payload;
|
|||
|
|
|
|||
|
|
// 按文档要求回包,避免云端重试;落库失败也不影响回包
|
|||
|
|
res.json({ bizRequestId: bizRequestId || "", code: 0 });
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const db = await getDatabase("Internet-hospital");
|
|||
|
|
const now = Date.now();
|
|||
|
|
|
|||
|
|
let orderId = "";
|
|||
|
|
let doctorCode = "";
|
|||
|
|
if (typeof roomId === "string" && roomId) {
|
|||
|
|
const order = await db.collection("consult-order").findOne(
|
|||
|
|
{ mrtcRoomId: roomId },
|
|||
|
|
{ projection: { orderId: 1, doctorCode: 1 } }
|
|||
|
|
);
|
|||
|
|
if (order && order.orderId) orderId = order.orderId;
|
|||
|
|
if (order && order.doctorCode) doctorCode = order.doctorCode;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const normalizedEventCode =
|
|||
|
|
typeof eventCode === "number"
|
|||
|
|
? eventCode
|
|||
|
|
: typeof eventCode === "string" && eventCode !== ""
|
|||
|
|
? Number(eventCode)
|
|||
|
|
: undefined;
|
|||
|
|
|
|||
|
|
const rr = recordResult && typeof recordResult === "object" ? recordResult : null;
|
|||
|
|
const status =
|
|||
|
|
rr && rr.status !== undefined && rr.status !== null && rr.status !== "" ? Number(rr.status) : undefined;
|
|||
|
|
|
|||
|
|
const $set = {
|
|||
|
|
updatedAt: now,
|
|||
|
|
source: "AUTO_RECORD_CALLBACK",
|
|||
|
|
};
|
|||
|
|
if (orderId) $set.orderId = orderId;
|
|||
|
|
if (doctorCode) $set.doctorCode = doctorCode;
|
|||
|
|
if (typeof roomId === "string" && roomId) $set.roomId = roomId;
|
|||
|
|
if (typeof recordId === "string" && recordId) $set.recordId = recordId;
|
|||
|
|
if (typeof bizRequestId === "string" && bizRequestId) $set.bizRequestId = bizRequestId;
|
|||
|
|
if (typeof bizName === "string" && bizName) $set.bizName = bizName;
|
|||
|
|
if (typeof appId === "string" && appId) $set.appId = appId;
|
|||
|
|
if (typeof workspaceId === "string" && workspaceId) $set.workspaceId = workspaceId;
|
|||
|
|
if (normalizedEventCode !== undefined && Number.isFinite(normalizedEventCode)) $set.eventCode = normalizedEventCode;
|
|||
|
|
|
|||
|
|
// eventCode=99 时传 recordResult:status/fileType/filePath/recordStartTime/mediaType
|
|||
|
|
if (normalizedEventCode === 99 && rr) {
|
|||
|
|
if (status !== undefined && Number.isFinite(status)) $set.status = status;
|
|||
|
|
if (rr.fileType !== undefined && rr.fileType !== null && rr.fileType !== "") $set.fileType = Number(rr.fileType);
|
|||
|
|
if (typeof rr.filePath === "string") $set.filePath = rr.filePath;
|
|||
|
|
if (rr.recordStartTime !== undefined && rr.recordStartTime !== null && rr.recordStartTime !== "")
|
|||
|
|
$set.startAt = Number(rr.recordStartTime);
|
|||
|
|
if (rr.mediaType !== undefined && rr.mediaType !== null && rr.mediaType !== "") $set.mediaType = Number(rr.mediaType);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// eventCode=11:录制结束(结束时间以回调到达时间为准)
|
|||
|
|
if (normalizedEventCode === 11) {
|
|||
|
|
$set.endAt = now;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 以 recordId 为主键幂等(回调不会携带 recordThirdId)
|
|||
|
|
const filter = recordId ? { recordId } : { bizRequestId: bizRequestId || `AUTO_${now}` };
|
|||
|
|
|
|||
|
|
await db.collection("mrtc-video-record").updateOne(
|
|||
|
|
filter,
|
|||
|
|
{
|
|||
|
|
// 注意:MongoDB 不允许在同一次 update 中同时用 $setOnInsert 与 $push 修改同一路径(会报 conflict)
|
|||
|
|
// events 字段由 $push 在首次写入时自动创建为数组
|
|||
|
|
$setOnInsert: { createdAt: now },
|
|||
|
|
$set,
|
|||
|
|
$push: {
|
|||
|
|
events: {
|
|||
|
|
event: "AUTO_RECORD_CALLBACK",
|
|||
|
|
at: now,
|
|||
|
|
eventCode: normalizedEventCode,
|
|||
|
|
status,
|
|||
|
|
payload,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{ upsert: true }
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
logger.info("[MRTC][AUTO_RECORD] callback stored", {
|
|||
|
|
bizRequestId,
|
|||
|
|
roomId,
|
|||
|
|
recordId,
|
|||
|
|
eventCode: normalizedEventCode,
|
|||
|
|
orderId,
|
|||
|
|
status,
|
|||
|
|
});
|
|||
|
|
} catch (e) {
|
|||
|
|
logger.error("[MRTC][AUTO_RECORD] callback store failed", {
|
|||
|
|
message: e && e.message ? e.message : String(e),
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.get(
|
|||
|
|
"/callback/data",
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = req.query;
|
|||
|
|
console.log(requestData);
|
|||
|
|
const item = await callBack.httpGet(requestData);
|
|||
|
|
safeSend(res, item, false);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.get(
|
|||
|
|
"/callback/command",
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = req.query;
|
|||
|
|
const item = await callBack.httpGet(requestData);
|
|||
|
|
safeSend(res, item, false);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/callback/command",
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = req.body;
|
|||
|
|
const item = await callBack.httpPost(requestData);
|
|||
|
|
safeSend(res, "success", false);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.use("/uploads", express.static(path.join(__dirname, "../uploads")));
|
|||
|
|
|
|||
|
|
app.use("/", express.static(path.join(__dirname, "./static")));
|
|||
|
|
|
|||
|
|
// 性能监控API端点
|
|||
|
|
app.get(
|
|||
|
|
"/monitor/stats",
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const stats = monitor.getStats();
|
|||
|
|
sendSuccess(res, stats, "性能统计获取成功");
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
app.post(
|
|||
|
|
"/monitor/reset",
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
monitor.reset();
|
|||
|
|
sendSuccess(res, null, "性能统计已重置");
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 数据库健康检查端点
|
|||
|
|
app.get(
|
|||
|
|
"/monitor/db-health",
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const isHealthy = await checkDatabaseHealth();
|
|||
|
|
if (isHealthy) {
|
|||
|
|
sendSuccess(res, { status: "healthy" }, "数据库连接正常");
|
|||
|
|
} else {
|
|||
|
|
sendError(res, "数据库连接异常", null, 503);
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 文件上传接口
|
|||
|
|
upload.uploadFile(app);
|
|||
|
|
|
|||
|
|
// 错误监控中间件
|
|||
|
|
app.use(errorMonitoringMiddleware);
|
|||
|
|
|
|||
|
|
// 全局错误处理中间件
|
|||
|
|
app.use((err, req, res, next) => {
|
|||
|
|
logger.error("全局错误:", err);
|
|||
|
|
// 记录错误到监控系统
|
|||
|
|
monitor.recordError(err, req);
|
|||
|
|
|
|||
|
|
if (!res.headersSent) {
|
|||
|
|
sendError(
|
|||
|
|
res,
|
|||
|
|
`服务器内部错误: ${err && err.message ? err.message : ''}`,
|
|||
|
|
err && err.message ? err.message : "Internal Server Error",
|
|||
|
|
500
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
app.post(
|
|||
|
|
"/getYoucanData/logout",
|
|||
|
|
asyncHandler(async (req, res) => {
|
|||
|
|
const requestData = req.body;
|
|||
|
|
requestData.type = 'loginout';
|
|||
|
|
const db = await getDatabase("corp");
|
|||
|
|
const item = await corp.main(requestData, db, req); // 传递 user 信息
|
|||
|
|
safeSend(res, item);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
useUploadFileParse(app);
|
|||
|
|
// 启动服务器
|
|||
|
|
const server = app.listen(PORT, "0.0.0.0", () => {
|
|||
|
|
logger.info(`Server is running on http://localhost:${PORT}`);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 应用服务器优化设置
|
|||
|
|
optimizeServer(server);
|
|||
|
|
|
|||
|
|
console.log(process.env.CONFIG_NODE_ENV);
|
|||
|
|
|
|||
|
|
// 定时任务
|
|||
|
|
trigger.initSchedule();
|
|||
|
|
|
|||
|
|
consultTrigger({
|
|||
|
|
type: "recoverDelayedTasks",
|
|||
|
|
});
|