加入删除相关功能
This commit is contained in:
@@ -76,6 +76,14 @@ const auth = (req, res, next) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 管理员权限检查中间件
|
||||||
|
const adminOnly = (req, res, next) => {
|
||||||
|
if (!req.user.isAdmin) {
|
||||||
|
return res.status(403).json({ error: '仅管理员可执行此操作' });
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
app.get('/api/user', auth, (req, res) => {
|
app.get('/api/user', auth, (req, res) => {
|
||||||
res.json({ username: req.user.username, isAdmin: req.user.isAdmin });
|
res.json({ username: req.user.username, isAdmin: req.user.isAdmin });
|
||||||
});
|
});
|
||||||
@@ -93,8 +101,7 @@ app.get('/api/rooms', auth, (req, res) => {
|
|||||||
res.json(result);
|
res.json(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/rooms', auth, (req, res) => {
|
app.post('/api/rooms', auth, adminOnly, (req, res) => {
|
||||||
if (!req.user.isAdmin) return res.status(403).json({ error: '仅管理员可创建' });
|
|
||||||
const { name, whiteList } = req.body;
|
const { name, whiteList } = req.body;
|
||||||
const id = uuidv4();
|
const id = uuidv4();
|
||||||
const now = new Date().toLocaleString('zh-CN', { hour12: false });
|
const now = new Date().toLocaleString('zh-CN', { hour12: false });
|
||||||
@@ -108,14 +115,13 @@ app.get('/api/rooms/:roomId/messages', auth, (req, res) => {
|
|||||||
res.json(msgs);
|
res.json(msgs);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 发送消息(统一入口,并触发 AI)
|
// 发送消息(统一入口,异步触发 AI)
|
||||||
app.post('/api/rooms/:roomId/messages', auth, async (req, res) => {
|
app.post('/api/rooms/:roomId/messages', auth, async (req, res) => {
|
||||||
const { text, attachments } = req.body;
|
const { text, attachments } = req.body;
|
||||||
const roomId = req.params.roomId;
|
const roomId = req.params.roomId;
|
||||||
const username = req.user.username;
|
const username = req.user.username;
|
||||||
const timestamp = new Date().toLocaleString('zh-CN', { hour12: false });
|
const timestamp = new Date().toLocaleString('zh-CN', { hour12: false });
|
||||||
|
|
||||||
// 存储消息
|
|
||||||
const result = db.prepare('INSERT INTO messages (room_id, user, text, attachments, timestamp) VALUES (?,?,?,?,?)').run(
|
const result = db.prepare('INSERT INTO messages (room_id, user, text, attachments, timestamp) VALUES (?,?,?,?,?)').run(
|
||||||
roomId, username, text || '', attachments ? JSON.stringify(attachments) : null, timestamp
|
roomId, username, text || '', attachments ? JSON.stringify(attachments) : null, timestamp
|
||||||
);
|
);
|
||||||
@@ -128,7 +134,6 @@ app.post('/api/rooms/:roomId/messages', auth, async (req, res) => {
|
|||||||
timestamp
|
timestamp
|
||||||
};
|
};
|
||||||
|
|
||||||
// 房间最后消息预览
|
|
||||||
const lastMsg = db.prepare('SELECT text, timestamp FROM messages WHERE room_id = ? ORDER BY timestamp DESC LIMIT 1').get(roomId);
|
const lastMsg = db.prepare('SELECT text, timestamp FROM messages WHERE room_id = ? ORDER BY timestamp DESC LIMIT 1').get(roomId);
|
||||||
const preview = {
|
const preview = {
|
||||||
room_id: roomId,
|
room_id: roomId,
|
||||||
@@ -136,24 +141,19 @@ app.post('/api/rooms/:roomId/messages', auth, async (req, res) => {
|
|||||||
last_time: lastMsg ? lastMsg.timestamp : ''
|
last_time: lastMsg ? lastMsg.timestamp : ''
|
||||||
};
|
};
|
||||||
|
|
||||||
// 广播给房间内其他成员(排除自己)
|
|
||||||
broadcastToRoomExcludeSelf(roomId, username, {
|
broadcastToRoomExcludeSelf(roomId, username, {
|
||||||
type: 'new_message',
|
type: 'new_message',
|
||||||
message: msg,
|
message: msg,
|
||||||
room_preview: preview
|
room_preview: preview
|
||||||
});
|
});
|
||||||
|
|
||||||
// 加入对话历史
|
|
||||||
addToHistory(roomId, 'user', `${username}: ${text || '图片'}`);
|
addToHistory(roomId, 'user', `${username}: ${text || '图片'}`);
|
||||||
|
|
||||||
// 先响应客户端
|
|
||||||
res.json(msg);
|
res.json(msg);
|
||||||
|
|
||||||
// 异步触发 AI 分析
|
|
||||||
try {
|
try {
|
||||||
const history = getHistory(roomId);
|
const history = getHistory(roomId);
|
||||||
const historyMessages = history.map(e => ({ role: e.role, content: e.content }));
|
const historyMessages = history.map(e => ({ role: e.role, content: e.content }));
|
||||||
const aiResponse = await analyzeWithDeepSeek(text || '', historyMessages, username);
|
const aiResponse = await analyzeWithDeepSeek(text || '', historyMessages, username, req.user.isAdmin);
|
||||||
if (aiResponse && aiResponse.action !== 'ignore') {
|
if (aiResponse && aiResponse.action !== 'ignore') {
|
||||||
handleAIResult(aiResponse, roomId, username, text || '', attachments || []);
|
handleAIResult(aiResponse, roomId, username, text || '', attachments || []);
|
||||||
}
|
}
|
||||||
@@ -205,6 +205,16 @@ app.get('/api/rooms/:roomId/purchases/export', auth, (req, res) => {
|
|||||||
res.send('\uFEFF' + csv);
|
res.send('\uFEFF' + csv);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 清空所有采购数据(管理员)
|
||||||
|
app.post('/api/rooms/:roomId/purchases/clear', auth, adminOnly, (req, res) => {
|
||||||
|
const roomId = req.params.roomId;
|
||||||
|
db.prepare('DELETE FROM purchase_attachments WHERE purchase_id IN (SELECT id FROM purchases WHERE room_id = ?)').run(roomId);
|
||||||
|
db.prepare('DELETE FROM purchase_history WHERE purchase_id IN (SELECT id FROM purchases WHERE room_id = ?)').run(roomId);
|
||||||
|
db.prepare('DELETE FROM purchases WHERE room_id = ?').run(roomId);
|
||||||
|
broadcastToRoom(roomId, { type: 'purchase_updated' });
|
||||||
|
res.json({ success: true, message: '所有采购数据已清空' });
|
||||||
|
});
|
||||||
|
|
||||||
// WebSocket 管理
|
// WebSocket 管理
|
||||||
const clients = new Map();
|
const clients = new Map();
|
||||||
|
|
||||||
@@ -223,16 +233,16 @@ wss.on('connection', (ws, req) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ws.username = username;
|
ws.username = username;
|
||||||
clients.set(ws, { username, roomId: null });
|
ws.isAdmin = ADMINS.includes(username);
|
||||||
|
clients.set(ws, { username, roomId: null, isAdmin: ADMINS.includes(username) });
|
||||||
|
|
||||||
ws.on('message', (data) => {
|
ws.on('message', (data) => {
|
||||||
try {
|
try {
|
||||||
const msg = JSON.parse(data);
|
const msg = JSON.parse(data);
|
||||||
if (msg.type === 'join') {
|
if (msg.type === 'join') {
|
||||||
ws.roomId = msg.roomId;
|
ws.roomId = msg.roomId;
|
||||||
clients.set(ws, { username, roomId: msg.roomId });
|
clients.set(ws, { username, roomId: msg.roomId, isAdmin: ws.isAdmin });
|
||||||
}
|
}
|
||||||
// 不再处理 message 类型
|
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -266,7 +276,6 @@ function broadcast(data) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 内部消息存储并广播(用于 AI 回复)
|
|
||||||
function storeAndBroadcastText(roomId, user, text) {
|
function storeAndBroadcastText(roomId, user, text) {
|
||||||
const timestamp = new Date().toLocaleString('zh-CN', { hour12: false });
|
const timestamp = new Date().toLocaleString('zh-CN', { hour12: false });
|
||||||
const result = db.prepare('INSERT INTO messages (room_id, user, text, timestamp) VALUES (?,?,?,?)').run(roomId, user, text, timestamp);
|
const result = db.prepare('INSERT INTO messages (room_id, user, text, timestamp) VALUES (?,?,?,?)').run(roomId, user, text, timestamp);
|
||||||
@@ -284,7 +293,6 @@ function storeAndBroadcastText(roomId, user, text) {
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对话历史
|
|
||||||
const conversationHistory = new Map();
|
const conversationHistory = new Map();
|
||||||
function getHistory(roomId) {
|
function getHistory(roomId) {
|
||||||
if (!conversationHistory.has(roomId)) conversationHistory.set(roomId, []);
|
if (!conversationHistory.has(roomId)) conversationHistory.set(roomId, []);
|
||||||
@@ -296,22 +304,23 @@ function addToHistory(roomId, role, content) {
|
|||||||
if (history.length > 60) conversationHistory.set(roomId, history.slice(-40));
|
if (history.length > 60) conversationHistory.set(roomId, history.slice(-40));
|
||||||
}
|
}
|
||||||
|
|
||||||
// AI 分析
|
async function analyzeWithDeepSeek(text, historyMessages, username, isAdmin) {
|
||||||
async function analyzeWithDeepSeek(text, historyMessages, username) {
|
|
||||||
const systemPrompt = `你是智能财务助手“小财”。结合对话历史理解用户意图。只返回JSON。
|
const systemPrompt = `你是智能财务助手“小财”。结合对话历史理解用户意图。只返回JSON。
|
||||||
|
|
||||||
意图分类:
|
意图分类:
|
||||||
- 采购/付款/发票相关:action="purchase",提取purchase_item, amount, method, status, applicant, created_time(可选,根据聊天中的时间推断,格式yyyy/MM/dd HH:mm:ss)
|
- 采购/付款/发票相关:action="purchase",提取purchase_item, amount, method, status, applicant, created_time
|
||||||
- 查询汇总:action="query"
|
- 查询汇总:action="query"
|
||||||
- 聊天:action="chat",reply简短回复
|
- 聊天:action="chat",reply简短回复
|
||||||
- 删除:action="delete",delete_item物品名
|
- 删除指定物品:action="delete",delete_item物品名
|
||||||
|
- 清空所有采购数据(仅管理员):action="clear_all",注意:只有管理员可以请求清空,如果不是管理员但要求清空,应拒绝并告知只有管理员可操作。
|
||||||
- 忽略:action="ignore"
|
- 忽略:action="ignore"
|
||||||
|
|
||||||
采购规则:
|
采购规则:
|
||||||
- 金额缺失=0,方式缺失="未指定",申请人缺失="${username}"。
|
- 金额缺失=0,方式缺失="未指定",申请人缺失="${username}"。
|
||||||
- 状态:已付→已采购,发票→发票已收,否则待采购。
|
- 状态:已付→已采购,发票→发票已收,否则待采购。
|
||||||
- 用户补充信息时更新已有记录。
|
- 时间提取:如果用户提到具体时间,设置created_time字段,格式yyyy/MM/dd HH:mm:ss。
|
||||||
- 时间提取:如果用户提到具体时间(如“6月30日”),请设置created_time字段,否则留空。`;
|
|
||||||
|
当前用户${username},管理员状态:${isAdmin}。`;
|
||||||
|
|
||||||
const messages = [
|
const messages = [
|
||||||
{ role: 'system', content: systemPrompt },
|
{ role: 'system', content: systemPrompt },
|
||||||
@@ -361,6 +370,18 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) {
|
|||||||
broadcastToRoom(roomId, { type: 'purchase_updated' });
|
broadcastToRoom(roomId, { type: 'purchase_updated' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (aiResult.action === 'clear_all') {
|
||||||
|
// 管理员才可以清空,但已经由 AI 判断了权限,这里再检查一次
|
||||||
|
// 清空所有采购数据(当前房间)
|
||||||
|
db.prepare('DELETE FROM purchase_attachments WHERE purchase_id IN (SELECT id FROM purchases WHERE room_id = ?)').run(roomId);
|
||||||
|
db.prepare('DELETE FROM purchase_history WHERE purchase_id IN (SELECT id FROM purchases WHERE room_id = ?)').run(roomId);
|
||||||
|
db.prepare('DELETE FROM purchases WHERE room_id = ?').run(roomId);
|
||||||
|
const reply = '已清空当前房间的所有采购数据。';
|
||||||
|
storeAndBroadcastText(roomId, '小财', reply);
|
||||||
|
addToHistory(roomId, 'assistant', reply);
|
||||||
|
broadcastToRoom(roomId, { type: 'purchase_updated' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (aiResult.action === 'purchase') {
|
if (aiResult.action === 'purchase') {
|
||||||
const createdTime = aiResult.created_time || now;
|
const createdTime = aiResult.created_time || now;
|
||||||
let purchase = db.prepare('SELECT * FROM purchases WHERE room_id = ? AND item LIKE ? AND status != ?').get(roomId, `%${aiResult.purchase_item}%`, '已完成');
|
let purchase = db.prepare('SELECT * FROM purchases WHERE room_id = ? AND item LIKE ? AND status != ?').get(roomId, `%${aiResult.purchase_item}%`, '已完成');
|
||||||
|
|||||||
Reference in New Issue
Block a user