// server.js
const express = require("express");
const multer = require("multer");
const cors = require("cors");
const fs = require("fs");
const path = require("path");
const OpenAI = require("openai");
const xml2js = require('xml2js');
const xmlparser = require('express-xml-bodyparser'); // 引入XML解析中间件
const https = require("https");
// 强制 IPv4 的 https agent
const ipv4Agent = new https.Agent({
family: 4, // 最关键:强制 IPv4
keepAlive: true,
maxSockets: 100,
keepAliveMsecs: 10000,
});
// 包一个强制 IPv4 的 fetch
const fetchIPv4 = (url, options = {}) => {
return fetch(url, {
...options,
agent: ipv4Agent, // 👈 加上 IPv4 专用 agent
});
};
const bodyParser = require("body-parser");
const axios = require('axios');
const crypto = require('crypto');
require('dotenv').config();
const app = express();
const port = 30009;
const TOKEN = 'my_token_123'; // 与公众号后台配置一致
const appid = process.env.WECHAT_APPID;
const secret = process.env.WECHAT_SECRET;
const accessTokenUrl = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`;
const mysql = require("mysql2/promise");
// 初始化数据库连接池
const pool = mysql.createPool({
host: "1.95.137.212",
user: "fastbee",
password: "maibu520",
database: "fastbee",
waitForConnections: true,
connectionLimit: 10,
});
// 允许大文件请求体
app.use(bodyParser.json({ limit: "500mb" }));
app.use(bodyParser.urlencoded({ limit: "500mb", extended: true }));
app.use(xmlparser({
explicitArray: false, // 禁用默认的数组格式,和你后续xml2js配置一致
trim: true // 去除字符串前后空格
}));
// Multer 上传目录
const upload = multer({
dest: "uploads/",
limits: { fileSize: 500 * 1024 * 1024 }, // 500MB
});
// Node <20 兼容 File
if (typeof File === "undefined") {
globalThis.File = require("node:buffer").File;
}
// 启用 CORS
app.use(cors());
// app.use(cors({
// origin: ["https://ai.satabot.com"], // 允许的域
// methods: "GET,POST,PUT,DELETE,OPTIONS",
// allowedHeaders: "Content-Type,Authorization",
// credentials: true
// }));
// app.options('*', cors());
app.use(express.json({ limit: "200mb" })); // 支持大 JSON
app.use(express.urlencoded({ extended: true })); // 支持 form-data 字段
// 初始化混元客户端
// const client = new OpenAI({
// apiKey: "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",
// baseURL: "https://openrouter.ai/api/v1",
// timeout: 120000,
// });
// const client = new OpenAI({
// baseURL:"https://openrouter.ai/api/v1",
// apiKey:"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",
// timeout: 120000
// });
// const client = new OpenAI({
// baseURL:"https://satabot.openai.azure.com/",
// apiKey:"DxvRiQWROzLTapiSHcK0FXKAAXDBlhci1XaqTTpr50sgUsAkmxfUJQQJ99BIACYeBjFXJ3w3AAABACOGZdHp",
// timeout: 120000
// });
//https://satabot.openai.azure.com/
// const client = new OpenAI({
// apiKey: "DxvRiQWROzLTapiSHcK0FXKAAXDBlhci1XaqTTpr50sgUsAkmxfUJQQJ99BIACYeBjFXJ3w3AAABACOGZdHp", // 推荐放环境变量
// baseURL: "https://satabot.openai.azure.com/openai/deployments/gpt-4.1/chat/completions?api-version=2025-01-01-preview",
// defaultQuery: { "api-version": "2024-02-15-preview" },
// defaultHeaders: { "api-key": process.env.AZURE_OPENAI_KEY },
// timeout: 120000
// });
// https://satabot.openai.azure.com/openai/deployments/gpt-4.1/
// https://satabot.openai.azure.com/openai/deployments/gpt-5-mini/
// 初始化客户端
const client = new OpenAI({
apiKey: "DxvRiQWROzLTapiSHcK0FXKAAXDBlhci1XaqTTpr50sgUsAkmxfUJQQJ99BIACYeBjFXJ3w3AAABACOGZdHp",
baseURL: "https://satabot.openai.azure.com/openai/deployments/gpt-4.1",
defaultQuery: { "api-version": "2024-04-01-preview" },
timeout: 120000,
fetch: fetchIPv4, // 关键点:把自定义 fetch 注入 SDK
// 确保底层也传 agent(可选,但保险)
baseOptions: {
agent: ipv4Agent
}
});
const client1 = new OpenAI({
apiKey: "DxvRiQWROzLTapiSHcK0FXKAAXDBlhci1XaqTTpr50sgUsAkmxfUJQQJ99BIACYeBjFXJ3w3AAABACOGZdHp",
baseURL: "https://satabot.openai.azure.com/openai/deployments/gpt-5-mini/",
defaultQuery: { "api-version": "2024-04-01-preview" },
timeout: 120000,
fetch: fetchIPv4, // 关键点:把自定义 fetch 注入 SDK
// 确保底层也传 agent(可选,但保险)
baseOptions: {
agent: ipv4Agent
}
});
async function test1()
{
console.time("test");
await client.chat.completions.create({
model: "gpt-4.1", // 注意:这里必须是你在 Azure portal 里创建的“部署名称”
messages: [
{ role: "user", content: "ping" }
]
});
console.timeEnd("test");
}
async function directCall()
{
console.time("directCall");
await client1.chat.completions.create({ model:"gpt-5-mini", messages:[{ role: "user", content: "ping" }] });
console.timeEnd("directCall");
}
test1();
directCall();
// 获取 access_token
// getAccessToken();
async function getAccessToken() {
const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`;
const res = await axios.get(url);
if (res.data.access_token) {
console.log('✅ 获取 access_token 成功',res.data);
return res.data.access_token;
} else {
throw new Error('❌ 获取 access_token 失败: ' + JSON.stringify(res.data));
}
}
const WECHAT_OAUTH_TOKEN_URL = 'https://api.weixin.qq.com/sns/oauth2/access_token';
app.get('/wechat/oauth/access-token', async (req, res) => {
try {
console.log(req.query)
// 1. 接收并校验前端传递的 code 参数
const { code } = req.query; // 从URL查询参数中获取 code
if (!code) {
return res.status(400).json({
code: -1,
message: '参数错误:缺少 code(微信授权临时票据)'
});
}
// 3. 调用微信 OAuth2 接口,传递必要参数
const response = await axios.get(WECHAT_OAUTH_TOKEN_URL, {
params: {
appid: "wx364b8ebf17fe45fe", // 你的微信appid
secret: "9b7476020991a2c71f1416da7f011d31", // 你的微信secret
code: code, // 前端传递的授权code
grant_type: 'authorization_code' // 固定值
}
});
// 4. 处理微信返回的结果
const wechatData = response.data;
// 微信接口失败(返回 errcode 字段)
if (wechatData.errcode) {
throw new Error(`微信接口错误:${wechatData.errmsg}(错误码:${wechatData.errcode})`);
}
// 5. 成功响应:返回微信返回的所有信息(包含 access_token、openid 等)
res.status(200).json({
code: 0,
message: '获取 OAuth2 access_token 成功',
data: wechatData // 包含 access_token、expires_in、refresh_token、openid 等
});
} catch (error) {
// 异常处理:捕获网络错误、微信接口错误等
console.error('获取 OAuth2 access_token 失败:', error.message);
res.status(500).json({
code: -3,
message: '获取 access_token 失败:' + error.message
});
}
});
/*菜单部分 */
async function createMenu() {
const accessToken = await getAccessToken();
const url = `https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${accessToken}`;
const target = encodeURIComponent("https://ai.satabot.com/?from=weixin");
const menuData = {
"button": [
{
"name": "产品中心",
"sub_button": [
{
"type": "view",
"name": "整机产品",
"url": "http://www.jsmaibu.com/products/"
},
{
"type": "view",
"name": "智能电控",
"url": "http://www.jsmaibu.com/Products/%E6%8E%A7%E5%88%B6%E5%99%A8%20MC100.html"
},
{
"type": "view",
"name": "资料下载",
"url": "http://www.jsmaibu.com/Support/Download.html"
},
// {
// "type": "click",
// "name": "AI智能体",
// "key": "AI_INTELLIGENT"
// }
{
"type": "view",
"name": "AI智能体",
// "url":`https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx364b8ebf17fe45fe&redirect_uri= encodeURIComponent("https://ai.satabot.com/?from=weixin")&response_type=code&scope=snsapi_login&state=123#wechat_redirect`,
"url": "https://ai.satabot.com/middle.html"
}
]
},
{
"name": "服务中心",
"sub_button": [
{
"type": "view",
"name": "解决方案",
"url": "http://www.jsmaibu.com/Solution.html"
},
{
"type": "view",
"name": "培训视频",
"url": "http://www.jsmaibu.com/Support/index.html"
},
{
"type": "view",
"name": "一键呼叫",
"url": "http://www.jsmaibu.com/contact/"
}
]
},
{
"name": "飒沓智能",
"sub_button": [
{
"type": "view",
"name": "关于我们",
"url": "http://www.jsmaibu.com/about-us.html#start"
},
{
"type": "view",
"name": "加入我们",
"url": "https://www.zhipin.com/gongsi/job/d1e30046e726d2d01nN72Ni4EFQ~.html?ka=all-jobs-hot"
}
]
}
]
};
const res = await axios.post(url, menuData);
if (res.data.errcode === 0) {
console.log('✅ 菜单创建成功');
} else {
console.error('❌ 菜单创建失败:', res.data);
}
}
// ========== 查询菜单 ==========
async function getMenu() {
const accessToken = await getAccessToken();
const url = `https://api.weixin.qq.com/cgi-bin/menu/get?access_token=${accessToken}`;
const res = await axios.get(url);
return res.data;
}
// ========== 删除菜单(可选)==========
async function deleteMenu() {
const accessToken = await getAccessToken();
const url = `https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=${accessToken}`;
const res = await axios.get(url);
return res.data;
}
app.get('/createMenu', async (req, res) => {
try {
await createMenu();
res.send('✅ 菜单创建成功!');
} catch (err) {
res.send('❌ 创建失败: ' + err.message);
}
});
app.get('/getMenu', async (req, res) => {
try {
const data = await getMenu();
res.json(data);
} catch (err) {
res.send('❌ 查询失败: ' + err.message);
}
});
app.get('/deleteMenu', async (req, res) => {
try {
const data = await deleteMenu();
res.json(data);
} catch (err) {
res.send('❌ 删除失败: ' + err.message);
}
});
app.post('/wechat', async (req, res) => {
try {
console.log("收到请求:", req.body);
const xml = req.body;
const message = xml.xml;
// 修复语法错误:移除msgtype前的空格
const msgType = message.msgtype;
const fromUser = message.fromusername; // openid
const toUser = message.tousername; // 公众号原始ID
console.log("消息类型:", msgType);
console.log("用户OpenID:", fromUser);
// 菜单点击事件
if (msgType === 'event' && message.event === 'CLICK') {
const key = message.eventkey;
console.log("菜单点击Key:", key);
if (key === 'AI_INTELLIGENT') {
try {
const timestamp = Date.now();
// 方案一:简单文本回复(推荐,避免超时)
const reply = `
${Date.now()}
立即进入AI智能体]]>
`;
res.send(reply);
return;
// 启动异步任务处理耗时操作
setTimeout(async () => {
try {
const token = await getAccessToken();
console.log("获取到Token:", token);
// 获取用户信息
const userRes = await axios.get('https://api.weixin.qq.com/sns/userinfo', {
params: {
access_token:token,
fromUser,
lang: 'zh_CN'
}
});
console.log(userRes, 'user000000000000');
// res.redirect('https://ai.satabot.com');
const { nickname, errcode: userErrCode } = userRes.data;
console.log(nickname, 'nickname');
return;
if (userErrCode) return res.status(500).send(`获取用户信息失败:${userErrCode}`);
const userInfoRes = await axios.get(
`https://api.weixin.qq.com/cgi-bin/user/info?access_token=${token}&openid=${fromUser}&lang=zh_CN`
);
const user = userInfoRes.data;
console.log("用户信息:", user);
// 使用客服消息接口发送详细回复
if (user && user.nickname) {
await sendCustomerServiceMessage(fromUser, `你好,${user.nickname}!欢迎使用AI智能体`);
}
} catch (err) {
console.error("异步处理错误:", err);
}
}, 0);
return;
// 方案二:直接回复(如果能确保5秒内完成)
/*
const token = await getAccessToken();
console.log("Token:", token);
const userInfoRes = await axios.get(
`https://api.weixin.qq.com/cgi-bin/user/info?access_token=${token}&openid=${fromUser}&lang=zh_CN`
);
const user = userInfoRes.data;
console.log("用户信息:", user);
const reply = `
${Date.now()}
`;
res.send(reply);
return;
*/
} catch (err) {
console.error("处理AI_INTELLIGENT错误:", err);
// 出错时返回标准回复
const errorReply = `
${Date.now()}
`;
res.send(errorReply);
return;
}
}
}
// 默认回复空串(表示不处理)
res.send('');
} catch (err) {
console.error("服务器错误:", err);
// 异常情况下回复空串
res.send('');
}
});
// app.post('/wechat', async (req, res) => {
// try {
// console.log(req,"收到恢复")
// const xml = req.body;
// console.log(xml);
// const message = xml.xml;
// const msgType = message.msgtype;
// const fromUser = message.fromusername; // openid
// // 菜单点击事件
// if (msgType === 'event' && message.event === 'CLICK') {
// const key = message.eventkey;
// if (key === 'AI_INTELLIGENT') {
// const redirectUri = encodeURIComponent(`https://ai.satabot.com/api/wechat/oauth/callback`);
// const state = fromUser; // 可用 openid 做状态标识
// console.log(state,"state")
// // const oauthUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_userinfo&state=${state}#wechat_redirect`;
// // console.log("oauthUrl",oauthUrl)
// // // 回复用户点击菜单提示,引导跳转
// // const reply = `
// //
// //
// //
// // ${Date.now()}
// //
// //
// //
// // `;
// // res.send(reply);
// // return;
// const token = await getAccessToken();
// console.log(token,"token")
// // 获取用户信息
// // https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=o6_bmjrPTlm6_2sgVt7hMZOPfL2M&lang=zh_CN
// const userInfoRes = await axios.get(`https://api.weixin.qq.com/cgi-bin/user/info?access_token=${token}&openid=${fromUser}&lang=zh_CN`);
// // https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=ENCODED_URL&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
// const user = userInfoRes.data;
// console.log(userInfoRes,'用户信息')
// // 回复文本消息
// const reply = `
//
//
//
// ${Date.now()}
//
//
//
// `;
// res.send(xml);
// return;
// }
// }
// // 默认回复空
// res.send('success');
// } catch (err) {
// console.error(err);
// res.send('success');
// }
// });
// OAuth2 回调
app.get('/wechat/oauth/callback', async (req, res) => {
try {
const { code, state } = req.query;
console.log(code,"code")
// 用 code 换取 access_token 和 openid
const tokenResp = await axios.get(
`https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appid}&secret=${secret}&code=${code}&grant_type=authorization_code`
);
const { access_token, openid } = tokenResp.data;
// 拉取用户信息
const userInfoResp = await axios.get(
`https://api.weixin.qq.com/sns/userinfo?access_token=${access_token}&openid=${openid}&lang=zh_CN`
);
const user = userInfoResp.data;
console.log(user, "获取到用户信息");
// 你可以直接跳转到前端页面并带上用户信息
res.redirect(`https://ai.satabot.com?nickname=${encodeURIComponent(user.nickname)}&openid=${openid}`);
} catch (err) {
console.error(err);
res.send("获取用户信息失败");
}
});
app.get("/test-db", async (req, res) => {
try {
await pool.query(
"INSERT INTO ai_chat_messages (session_id, role, content) VALUES (?, 'user', ?)",
["sess1", "hello"]
);
res.send("OK");
} catch (err) {
console.error(err);
res.send("FAIL");
}
});
app.get("/wechat/userinfo", async (req, res) => {
const { code } = req.query;
if (!code) return res.status(400).send("缺少 code");
try {
// 1. 用 code 换取 access_token 和 openid
const tokenResp = await axios.get(
`https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appid}&secret=${secret}&code=${code}&grant_type=authorization_code`
);
const { access_token, openid } = tokenResp.data;
// 2. 拉取用户信息
const userRes = await axios.get(
`https://api.weixin.qq.com/sns/userinfo?access_token=${access_token}&openid=${openid}&lang=zh_CN`
);
const userInfo = userRes.data;
// 3. 返回给前端
res.json({ code: 0, data: userInfo });
} catch (err) {
console.error(err);
res.status(500).send("获取用户信息失败");
}
});
app.post("/user", async (req, res) => {
try {
const { userId } = req.body; // 从请求体里拿到 user_id
if (!userId&&userId!="NaN") {
res.json({
code: 300,
error: "http://ri.satabot.com/#/login?goRemote=1",
msg:"未找到该用户"
});
return;
}
// 查询 user_name
const [rows] = await pool.query(
"SELECT user_name FROM sys_user WHERE user_id = ?",
[userId]
);
if (rows.length === 0) {
res.json({
code: 300,
error: "http://ri.satabot.com/#/login?goRemote=1",
msg:"未找到该用户"
});
return;
}
res.json({ code:0,userId, userName: rows[0].user_name });
} catch (err) {
console.error(err);
res.status(500).send("服务器错误");
}
});
app.post("/getuserByopenid", async (req, res) => {
try {
const { userId } = req.body; // 从请求体里拿到 user_id
if (!userId) {
return res.status(400).send("缺少 user_id");
}
// 查询 user_name
const [rows] = await pool.query(
"SELECT * FROM sys_user WHERE open_id = ?",
[userId]
);
if (rows.length > 0) {
// 用户存在,直接返回
res.json({ code: 0, msg: "找到该用户",userId:rows[0].user_id });
return
} else {
// 用户不存在,插入新用户
const [result] = await pool.query(
"INSERT INTO sys_user (open_id,user_name,nick_name) VALUES (?,?,?)",
[userId,userId,userId]
);
console.log(result,"udgwehvdh")
res.json({code:0,msg:"新增该用户",userId:result.insertId});
return ;
}
} catch (err) {
console.error(err);
res.status(500).send("服务器错误");
}
});
app.get('/wechat', (req, res) => {
const { signature, timestamp, nonce, echostr } = req.query;
console.log(req, "req");
const arr = [TOKEN, timestamp, nonce].sort();
const str = arr.join('');
const hash = crypto.createHash('sha1').update(str).digest('hex');
if (hash === signature) res.send(echostr);
else res.send('验证失败');
});
// 查询用户所有会话列表
app.get("/sessions", async (req, res) => {
console.log("收到的 userId:", req.query.userId);
try {
const userId = req.query.userId;
if (!userId || userId == "NaN" || isNaN(Number(userId)))
{
console.log("userId:", req.query.userId);
res.json({
code: 300,
// error:'https://gsgg.com'
// error: "http://ri.satabot.com/#/dhhdghgbshwadhg"
error: "http://ri.satabot.com/#/login?goRemote=1"
});
return;
// return res.status(500).json({ code: 505, error: "http://1.95.137.212/#/login?redirect=%2Findex&goRemote=1" });
// return res.redirect('http://1.95.137.212/#/login?redirect=%2Findex&goRemote=1');
}
// return;
// 查该用户所有的会话,按最后活跃时间倒序
// const [rows] = await pool.query(
// `
// SELECT session_id,title,MAX(created_at) AS last_time
// FROM ai_chat_messages
// WHERE user_id = ?
// GROUP BY session_id
// ORDER BY last_time DESC
// `,
// [userId]
// );
// const [rows] = await pool.query(`
// SELECT m.session_id, m.title
// FROM ai_chat_messages m
// INNER JOIN (
// SELECT session_id, MAX(created_at) AS last_time
// FROM ai_chat_messages
// WHERE user_id = ?
// GROUP BY session_id
// ) t ON m.session_id = t.session_id AND m.created_at = t.last_time
// WHERE m.user_id = ?
// ORDER BY t.last_time ASC
// `, [userId, userId]);
const [rows] = await pool.query(`
SELECT session_id, title
FROM ai_chat_messages
WHERE user_id = ?
AND title IS NOT NULL
AND title != ''
GROUP BY session_id
ORDER BY MIN(created_at) ASC
`, [userId]);
res.json({
code: 0,
sessions: rows.map(r => ({
sessId: r.session_id,
title: r.title
}))
});
}
catch (err)
{
console.error("查询会话列表失败:", err);
res.status(500).json({ code: 1, error: err.message });
}
});
// 查询会话历史
app.get("/history", async (req, res) => {
try
{
if(!req.query?.sessionId || req.query?.sessionId == "NaN")
{
return res.status(400).json({ code: 1, error: "sessionId 必填" });
}
const { sessionId } = req.query;
// 查询数据库
const [rows] = await pool.query(
"SELECT user_id, role, content, image_base64, created_at,title FROM ai_chat_messages WHERE session_id = ? ORDER BY id ASC",
[sessionId]
);
res.json({
code: 0,
sessionId,
messages: rows,
});
} catch (err) {
console.error("查询会话历史失败:", err);
res.status(500).json({ code: 1, error: err.message });
}
});
// 删除指定会话及其消息
app.delete("/session", async (req, res) => {
try
{
console.log("会话删除:", req.query);
// if (!sessionId || !userId)
if (req.query?.userId == "NaN" || !req.query?.userId || !req.query?.sessionId || req.query?.sessionId == "NaN")
{
return res.status(400).json({ code: 1, error: "sessionId 和 userId 必填" });
}
const { sessionId, userId } = req.query;
// 确保只能删除自己的会话
const [rows] = await pool.query(
"SELECT COUNT(*) as cnt FROM ai_chat_messages WHERE session_id = ? AND user_id = ?",
[sessionId, userId]
);
if (rows[0].cnt === 0) {
return res.status(404).json({ code: 1, error: "会话不存在或无权限" });
}
// 删除该 session 的所有消息
await pool.query(
"DELETE FROM ai_chat_messages WHERE session_id = ? AND user_id = ?",
[sessionId, userId]
);
res.json({
code: 0,
message: `会话 ${sessionId} 已删除`,
});
}
catch (err)
{
console.error("删除会话失败:", err);
res.status(500).json({ code: 1, error: err.message });
}
});
// 新建会话(实际上就是往消息表里插入一条记录)
app.post("/session", async (req, res) => {
try
{
console.log("新建会话: ",req.body)
if (req.body?.userId == "NaN" || !req.body?.userId || !req.body?.sessionId || req.body?.sessionId == "NaN")
{
console.log("=== userId: ",req.body?.userId)
return res.status(400).json({ code: 1, error: "userId 和 sessionId 必填" });
}
const { userId, sessionId } = req.body;
// 插入一条“新会话”的首条消息,可以是空内容
await pool.query(
"INSERT INTO ai_chat_messages (user_id, session_id, role, content) VALUES (?, ?, 'system', '')",
[userId, sessionId]
);
res.json({
code: 0,
message: `会话 ${sessionId} 已创建`,
});
}
catch (err)
{
console.error("创建会话失败:", err);
res.status(500).json({ code: 1, error: err.message });
}
});
const stopFlags = new Map();
// ====================== API ======================
app.post("/analyze", upload.single("file"), async (req, res) => {
try
{
const { requestId } = req.body;
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.setHeader("Transfer-Encoding", "chunked");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
let aborted = false;
req.on("close", () => {
console.log("🔴 前端断开连接,停止整个分析流程");
aborted = true;
});
console.time("analyze");
const logBody = { ...req.body };
if(req.body?.imageBase64)
{
delete logBody.imageBase64;
}
console.log("analyze: ", logBody);
// console.log("analyze: ",req.body)
if (!req.body?.userId || !req.body?.sessionId)
{
console.log("缺少必要参数");
return res.status(400).json({ error: "缺少必要参数" });
}
const { userPrompt, imageBase64, userId, sessionId } = req.body;
const [rows] = await pool.query(
`
SELECT EXISTS(
SELECT 1
FROM ai_chat_messages
WHERE session_id = ?
AND user_id = ?
AND title IS NOT NULL
AND title != ''
) AS hasTitle
`,
[sessionId, userId]
);
const existingTitle = rows[0].hasTitle === 1; // true / false
console.log(existingTitle, rows?.[0], 9999, rows)
// 如果前端没有传 sessionId,生成一个
const sessId = sessionId || Date.now().toString();
// ------------------ 获取历史对话(上下文) ------------------
const [historyRows] = await pool.query(
"SELECT role, content, image_base64 FROM ai_chat_messages WHERE session_id = ? AND user_id = ? ORDER BY created_at ASC",
[sessId, userId || null]
);
// 构建历史消息数组
const historyMessages = historyRows.map(row => {
const message = {
role: row.role,
content: []
};
// 添加文本内容
if (row.content)
{
message.content.push({ type: "text", text: row.content });
}
// 添加图片内容
if (row.image_base64)
{
message.content.push({
type: "image_url",
image_url: {
url: row.image_base64.startsWith("data:")
? row.image_base64
: `data:image/png;base64,${row.image_base64}`,
}
});
}
return message;
});
// ------------------ 保存用户提问 ------------------
await pool.query(
"INSERT INTO ai_chat_messages (session_id, user_id, role, content, image_base64) VALUES (?, ?, 'user', ?, ?)",
[sessId, userId || null, userPrompt || "", imageBase64 || null]
);
// console.log("userPrompt: ",userPrompt);
// console.log("imageBase64: ",imageBase64);
// console.log("userId: ",userId);
// console.log("sessionId: ",sessionId);
// ------------------ 图像分析 ------------------
// 构建包含上下文的消息数组
const imageAnalysisMessages = [
{
role: "system",
content: "你是一名图像分析专家,需要结合历史对话内容分析当前图像。"
},
...historyMessages, // 添加历史对话作为上下文
{
role: "user",
content: [
{ type: "text", text: userPrompt || "请描述图片内容。" },
imageBase64
? {
type: "image_url",
image_url: {
url: imageBase64.startsWith("data:")
? imageBase64
: `data:image/png;base64,${imageBase64}`,
},
}
: null,
].filter(Boolean),
},
];
const completion = await client.chat.completions.create({
model: "gpt-4.1",
stream: true,
messages: imageAnalysisMessages
});
console.log("====== 开始图像分析... ======")
let sceneText = "";
/* TLS 加密缓冲,小块数据可能先缓存在服务器端,不立即发送,会导致图像分析模型的结果不能立即返回 */
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
});
res.socket.setNoDelay(true);
for await (const chunk of completion)
{
if (stopFlags.get(requestId)) {
console.log("⛔ 图像分析被前端中断");
res.end();
stopFlags.delete(requestId); // 用完清理
return;
}
const delta = chunk.choices[0]?.delta?.content || "";
sceneText += delta;
if (delta)
{
res.write(
`data: ${JSON.stringify({ type: "scene", content: delta })}\n\n`
);
}
// if (delta)
// {
// for (const ch of delta)
// {
// res.write(`data: ${JSON.stringify({ type: "scene", content: ch })}\n\n`);
// await new Promise(r => setTimeout(r, 0)); // 控制渲染节奏
// }
// }
}
console.log("====== 图像分析结束 ======")
// 保存图像分析结果到数据库
// await pool.query(
// "INSERT INTO ai_chat_messages (session_id, user_id, role, content) VALUES (?, ?, 'assistant', ?)",
// [sessId, userId || null, sceneText]
// );
// ------------------ 3️⃣ LLM 分析 ------------------
// console.log("LLM 分析 sceneText: ", sceneText)
// const preText = "详细分析这个光伏场站,根据图片给出详细的运维方案"
// + "如果这个光伏场站需要割草怎么做,给出一份详细的计划"
// + "如果这个光伏场站需要用无人机清洗怎么做,给出一份详细的计划"
// + "如果这个光伏场站需要巡检怎么做,给出一份详细的计划"
// 构建包含完整上下文的LLM消息数组
const llmMessages = [
{
role: "system",
content: "你是一名新能源项目的图像分析AI分析师,需要结合历史对话提供专业建议。"
},
...historyMessages, // 添加历史对话作为上下文
{
role: "user",
content: userPrompt || "请基于图片分析提供建议。"
},
{
role: "assistant",
content: sceneText // 添加当前图像分析结果
}
];
const llmStream = await client1.chat.completions.create({
model: "gpt-5-mini",
stream: true,
messages: llmMessages
});
console.log("====== 开始深度思考... ======")
let llmText = "";
for await (const chunk of llmStream)
{
if (stopFlags.get(requestId)) {
console.log("⛔ 深度分析被前端中断");
res.end();
stopFlags.delete(requestId);
return;
}
const delta = chunk.choices[0]?.delta?.content || "";
llmText += delta;
if (delta)
res.write(
`data: ${JSON.stringify({ type: "llm", content: delta})}\n\n`
);
/* delta 很长,这会 人为拉长处理时间。 */
// if (delta)
// {
// for (const ch of delta)
// {
// res.write(`data: ${JSON.stringify({ type: "llm", content: ch })}\n\n`);
// await new Promise(r => setTimeout(r, 0)); // 控制渲染节奏
// }
// }
}
// 保存 LLM 输出到数据库
/* 加标题需要异步,且在会话开始时就进行 */
if (existingTitle) {
console.log("uidh")
await pool.query(
"INSERT INTO ai_chat_messages (session_id, user_id, role, content) VALUES (?, ?, 'assistant', ?)",
[sessId, userId || null, llmText]
);
} else {
console.log("uidh1111")
try
{
const titlePrompt = `请根据以下对话内容生成一个简短的标题(不超过15字):
用户问题: ${userPrompt}
AI回答: ${llmText}`;
const titleResp = await client1.chat.completions.create({
model: "gpt-5-mini",
messages: [
{ role: "system", content: "你是标题生成助手,只返回标题,不要解释。" },
{ role: "user", content: titlePrompt }
]
});
const title = titleResp.choices[0]?.message?.content?.trim() || "未命名会话";
res.write(
`data: ${JSON.stringify({ type: "title", content: title })}\n\n`
);
console.log("生成的标题:", title);
// 保存到数据库(建议你给 ai_chat_messages 表新增一列 title,或者单独建一张会话表)
await pool.query(
"INSERT INTO ai_chat_messages (session_id, user_id, role, content,title) VALUES (?, ?, 'assistant', ?,?)",
[sessId, userId || null, llmText,title]
);
}
catch (err)
{
console.error("生成标题失败:", err.message);
}
}
res.write("data: [DONE]\n\n");
res.end();
// 删除上传文件
if (req.file && req.file.path)
{
fs.unlinkSync(req.file.path);
}
console.log("====== 分析完成 ======");
console.timeEnd("analyze");
}
catch (err)
{
console.error("❌ 调用失败:", err.response?.data || err.message);
res.status(500).json({ code: 1, error: err.response?.data || err.message });
}
});
app.post("/stop", express.json(), (req, res) => {
const { requestId } = req.body;
console.log("🔴 收到停止信号:", requestId);
stopFlags.set(requestId, true);
console.log(stopFlags,"消息id");
res.json({ ok: true });
});
app.post('/submit',async (req, res) => {
const { company, scenes, needs, userId } = req.body;
console.log("收到数据:", company, scenes, needs, userId);
if (!company || !needs || !userId) {
return res.status(400).json({ message: "缺少必要字段" });
}
try {
const sql =
"INSERT INTO pv_form (company, scenes, needs, userId) VALUES (?, ?, ?, ?)";
await pool.query(sql, [company, scenes, needs, userId || null]);
res.json({ code:200,message: "提交成功!" });
} catch (err) {
console.error("数据库插入失败:", err);
res.status(500).json({ message: "数据库写入失败" });
}
});
// // 获取微信授权回调接口
// app.get("/callback", async (req, res) => {
// const code = req.query.code;
// try {
// // 用 code 换 openid
// const resp = await axios.get("https://api.weixin.qq.com/sns/oauth2/access_token", {
// params: {
// appid: APPID,
// secret: APPSECRET,
// code: code,
// grant_type: "authorization_code",
// },
// });
// const data = resp.data;
// console.log("微信授权返回:", data);
// if (data.openid) {
// res.json({
// code: 0,
// openid:data.openid
// })
// // 判断数据库里是否已经有这个 openid
// // 没有 → 注册;有 → 登录
// // res.send(`授权成功,openid: ${data.openid}`);
// } else {
// res.json({
// code: 500,
// msg:"微信授权失败"+ JSON.stringify(data)
// })
// // res.send("授权失败: " + JSON.stringify(data));
// }
// } catch (err) {
// console.error(err);
// res.send("服务器错误");
// }
// });
// 静态文件服务
app.use("/uploads", express.static("uploads"));
// 启动服务
app.listen(port, () => {
console.log(`✅ 服务器运行:http://localhost:${port}`);
});