更改消息时间
This commit is contained in:
100
server/server.js
100
server/server.js
@@ -22,6 +22,12 @@ const ADMINS = (process.env.ADMINS || '').split(',');
|
||||
const DEEPSEEK_API_KEY = process.env.DEEPSEEK_API_KEY;
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'xiaocai-secret-' + Date.now();
|
||||
|
||||
// 时区:亚洲/上海
|
||||
const TIMEZONE = 'Asia/Shanghai';
|
||||
function timestamp() {
|
||||
return new Date().toLocaleString('zh-CN', { timeZone: TIMEZONE, hour12: false });
|
||||
}
|
||||
|
||||
// 初始化用户
|
||||
const insertUser = db.prepare('INSERT OR IGNORE INTO users (username, password, is_admin) VALUES (?, ?, ?)');
|
||||
for (const [username, password] of Object.entries(USERS)) {
|
||||
@@ -101,12 +107,35 @@ app.get('/api/rooms', auth, (req, res) => {
|
||||
app.post('/api/rooms', auth, adminOnly, (req, res) => {
|
||||
const { name, whiteList } = req.body;
|
||||
const id = uuidv4();
|
||||
const now = new Date().toLocaleString('zh-CN', { hour12: false });
|
||||
const now = timestamp();
|
||||
db.prepare('INSERT INTO rooms (id, name, created_by, white_list, created_at) VALUES (?,?,?,?,?)').run(id, name, req.user.username, whiteList || '', now);
|
||||
broadcast({ type: 'room_created', room: { id, name, white_list: whiteList || '' } });
|
||||
res.json({ id, name });
|
||||
});
|
||||
|
||||
// 更新群聊信息(管理员)
|
||||
app.put('/api/rooms/:roomId', auth, adminOnly, (req, res) => {
|
||||
const { name, whiteList } = req.body;
|
||||
const roomId = req.params.roomId;
|
||||
const room = db.prepare('SELECT * FROM rooms WHERE id = ?').get(roomId);
|
||||
if (!room) return res.status(404).json({ error: '群聊不存在' });
|
||||
db.prepare('UPDATE rooms SET name = ?, white_list = ? WHERE id = ?').run(name, whiteList || '', roomId);
|
||||
broadcast({ type: 'room_updated', room: { id: roomId, name, white_list: whiteList || '' } });
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// 删除群聊(管理员)
|
||||
app.delete('/api/rooms/:roomId', auth, adminOnly, (req, res) => {
|
||||
const roomId = req.params.roomId;
|
||||
db.prepare('DELETE FROM messages WHERE room_id = ?').run(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);
|
||||
db.prepare('DELETE FROM rooms WHERE id = ?').run(roomId);
|
||||
broadcast({ type: 'room_deleted', roomId });
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
app.get('/api/rooms/:roomId/messages', auth, (req, res) => {
|
||||
const msgs = db.prepare('SELECT * FROM messages WHERE room_id = ? ORDER BY timestamp ASC').all(req.params.roomId);
|
||||
res.json(msgs);
|
||||
@@ -117,10 +146,10 @@ app.post('/api/rooms/:roomId/messages', auth, async (req, res) => {
|
||||
const { text, attachments } = req.body;
|
||||
const roomId = req.params.roomId;
|
||||
const username = req.user.username;
|
||||
const timestamp = new Date().toLocaleString('zh-CN', { hour12: false });
|
||||
const now = timestamp();
|
||||
|
||||
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, now
|
||||
);
|
||||
const msg = {
|
||||
id: result.lastInsertRowid,
|
||||
@@ -128,7 +157,7 @@ app.post('/api/rooms/:roomId/messages', auth, async (req, res) => {
|
||||
user: username,
|
||||
text: text || '',
|
||||
attachments: attachments || [],
|
||||
timestamp
|
||||
timestamp: now
|
||||
};
|
||||
|
||||
const lastMsg = db.prepare('SELECT text, timestamp FROM messages WHERE room_id = ? ORDER BY timestamp DESC LIMIT 1').get(roomId);
|
||||
@@ -208,15 +237,6 @@ app.get('/api/rooms/:roomId/purchases/export', auth, (req, res) => {
|
||||
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 管理
|
||||
const clients = new Map();
|
||||
wss.on('connection', (ws, req) => {
|
||||
@@ -278,54 +298,35 @@ function broadcast(data) {
|
||||
}
|
||||
|
||||
function storeAndBroadcastText(roomId, user, text) {
|
||||
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 msg = { id: result.lastInsertRowid, room_id: roomId, user, text, attachments: [], timestamp };
|
||||
const now = timestamp();
|
||||
const result = db.prepare('INSERT INTO messages (room_id, user, text, timestamp) VALUES (?,?,?,?)').run(roomId, user, text, now);
|
||||
const msg = { id: result.lastInsertRowid, room_id: roomId, user, text, attachments: [], timestamp: now };
|
||||
const lastMsg = db.prepare('SELECT text, timestamp FROM messages WHERE room_id = ? ORDER BY timestamp DESC LIMIT 1').get(roomId);
|
||||
broadcastToRoom(roomId, {
|
||||
type: 'new_message',
|
||||
message: msg,
|
||||
room_preview: {
|
||||
room_id: roomId,
|
||||
last_message: lastMsg ? lastMsg.text : '',
|
||||
last_time: lastMsg ? lastMsg.timestamp : ''
|
||||
}
|
||||
room_preview: { room_id: roomId, last_message: lastMsg ? lastMsg.text : '', last_time: lastMsg ? lastMsg.timestamp : '' }
|
||||
});
|
||||
return msg;
|
||||
}
|
||||
|
||||
// ---------- 待处理操作管理 ----------
|
||||
const pendingActions = new Map(); // key: `${roomId}:${username}`, value: { type, data }
|
||||
|
||||
function hasPendingAction(roomId, username) {
|
||||
return pendingActions.has(`${roomId}:${username}`);
|
||||
}
|
||||
|
||||
function setPendingAction(roomId, username, action) {
|
||||
pendingActions.set(`${roomId}:${username}`, action);
|
||||
}
|
||||
|
||||
function clearPendingAction(roomId, username) {
|
||||
pendingActions.delete(`${roomId}:${username}`);
|
||||
}
|
||||
|
||||
const pendingActions = new Map();
|
||||
function hasPendingAction(roomId, username) { return pendingActions.has(`${roomId}:${username}`); }
|
||||
function setPendingAction(roomId, username, action) { pendingActions.set(`${roomId}:${username}`, action); }
|
||||
function clearPendingAction(roomId, username) { pendingActions.delete(`${roomId}:${username}`); }
|
||||
function executePendingAction(roomId, username) {
|
||||
const action = pendingActions.get(`${roomId}:${username}`);
|
||||
if (!action) return false;
|
||||
clearPendingAction(roomId, username);
|
||||
|
||||
const now = new Date().toLocaleString('zh-CN', { hour12: false });
|
||||
const now = timestamp();
|
||||
try {
|
||||
if (action.type === 'delete') {
|
||||
const { itemName, purchaseIds } = action.data;
|
||||
const deleteAttachments = db.prepare('DELETE FROM purchase_attachments WHERE purchase_id = ?');
|
||||
const deleteHistory = db.prepare('DELETE FROM purchase_history WHERE purchase_id = ?');
|
||||
const deletePurchase = db.prepare('DELETE FROM purchases WHERE id = ?');
|
||||
for (const id of purchaseIds) {
|
||||
deleteAttachments.run(id);
|
||||
deleteHistory.run(id);
|
||||
deletePurchase.run(id);
|
||||
}
|
||||
for (const id of purchaseIds) { deleteAttachments.run(id); deleteHistory.run(id); deletePurchase.run(id); }
|
||||
const reply = `✅ 已删除采购记录:${itemName}(共 ${purchaseIds.length} 条)`;
|
||||
storeAndBroadcastText(roomId, '小财', reply);
|
||||
addToHistory(roomId, 'assistant', reply);
|
||||
@@ -339,14 +340,10 @@ function executePendingAction(roomId, username) {
|
||||
}
|
||||
broadcastToRoom(roomId, { type: 'purchase_updated' });
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('执行待处理操作失败:', e);
|
||||
storeAndBroadcastText(roomId, '小财', '❌ 操作执行失败,请重试。');
|
||||
return false;
|
||||
}
|
||||
} catch (e) { console.error('执行待处理操作失败:', e); storeAndBroadcastText(roomId, '小财', '❌ 操作执行失败,请重试。'); return false; }
|
||||
}
|
||||
|
||||
// 对话历史
|
||||
// ---------- 对话历史 ----------
|
||||
const conversationHistory = new Map();
|
||||
function getHistory(roomId) {
|
||||
if (!conversationHistory.has(roomId)) conversationHistory.set(roomId, []);
|
||||
@@ -358,11 +355,12 @@ function addToHistory(roomId, role, content) {
|
||||
if (history.length > 60) conversationHistory.set(roomId, history.slice(-40));
|
||||
}
|
||||
|
||||
// ---------- AI 函数 ----------
|
||||
async function analyzeWithDeepSeek(text, historyMessages, username, isAdmin) {
|
||||
const systemPrompt = `你是智能财务助手“小财”。结合对话历史理解用户意图。只返回JSON。
|
||||
|
||||
意图分类:
|
||||
- 采购/付款/发票相关:action="purchase",提取purchase_item, amount, method, status, applicant, created_time
|
||||
- 采购/付款/发票相关:action="purchase",提取purchase_item, amount, method, status, applicant, created_time(可选,根据聊天中的时间推断,格式yyyy/MM/dd HH:mm:ss)
|
||||
- 查询汇总:action="query"
|
||||
- 聊天:action="chat",reply简短回复
|
||||
- 删除指定物品:action="delete",delete_item为物品名
|
||||
@@ -399,7 +397,7 @@ async function analyzeWithDeepSeek(text, historyMessages, username, isAdmin) {
|
||||
}
|
||||
|
||||
function handleAIResult(aiResult, roomId, username, originalText, attachments) {
|
||||
const now = new Date().toLocaleString('zh-CN', { hour12: false });
|
||||
const now = timestamp();
|
||||
|
||||
if (aiResult.action === 'ask') {
|
||||
storeAndBroadcastText(roomId, '小财', aiResult.question);
|
||||
@@ -416,7 +414,6 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) {
|
||||
addToHistory(roomId, 'assistant', `没有找到与“${itemName}”相关的采购记录。`);
|
||||
return;
|
||||
}
|
||||
// 构建确认消息
|
||||
let confirmText = `⚠️ 即将删除以下 ${purchases.length} 条采购记录,请回复“确认”继续:\n\n`;
|
||||
purchases.forEach(p => {
|
||||
confirmText += `• ${p.item} | ¥${p.amount} | ${p.status} | ${p.applicant} | ${p.created_at}\n`;
|
||||
@@ -424,7 +421,6 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) {
|
||||
confirmText += `\n如果不删除,请忽略此消息。`;
|
||||
storeAndBroadcastText(roomId, '小财', confirmText);
|
||||
addToHistory(roomId, 'assistant', confirmText);
|
||||
// 存储待处理操作
|
||||
setPendingAction(roomId, username, {
|
||||
type: 'delete',
|
||||
data: { itemName, purchaseIds: purchases.map(p => p.id) }
|
||||
|
||||
Reference in New Issue
Block a user