/** * 聊天记录管理API - 后台管理 * 提供聊天记录的查询和管理功能 */ const Framework = require("../../framework/node-core-framework.js"); module.exports = { /** * @swagger * /admin_api/chat/list: * post: * summary: 获取聊天记录列表 * description: 分页获取所有聊天记录 * tags: [后台-聊天管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * page: * type: integer * description: 页码 * pageSize: * type: integer * description: 每页数量 * chatType: * type: string * description: 聊天类型(可选) * hasReply: * type: boolean * description: 是否有回复(可选) * responses: * 200: * description: 获取成功 */ 'POST /chat/list': async (ctx) => { const models = Framework.getModels(); const { chat_records, op } = models; const body = ctx.getBody(); // 支持两种参数格式:直接传参或通过 seachOption 传递 const seachOption = body.seachOption || {}; const pageOption = body.pageOption || {}; // 获取分页参数 const page = pageOption.page || body.page || 1; const pageSize = pageOption.pageSize || body.pageSize || 20; const limit = pageSize; const offset = (page - 1) * pageSize; const where = {}; // 支持平台筛选 if (seachOption.platform || body.platform) { where.platform = seachOption.platform || body.platform; } // 支持聊天类型筛选 if (seachOption.chatType || body.chatType) { where.chatType = seachOption.chatType || body.chatType; } // 支持回复状态筛选 if (seachOption.hasReply !== undefined || body.hasReply !== undefined) { where.hasReply = seachOption.hasReply !== undefined ? seachOption.hasReply : body.hasReply; } // 支持搜索:公司名称、职位名称、消息内容 if (seachOption.key && seachOption.value) { const key = seachOption.key; const value = seachOption.value; if (key === 'companyName') { where.companyName = { [op.like]: `%${value}%` }; } else if (key === 'jobTitle') { where.jobTitle = { [op.like]: `%${value}%` }; } else if (key === 'content') { where.content = { [op.like]: `%${value}%` }; } else if (key === 'sn_code') { where.sn_code = { [op.like]: `%${value}%` }; } else { // 默认搜索内容 where.content = { [op.like]: `%${value}%` }; } } // 支持直接搜索文本 if (body.searchText) { where[op.or] = [ { companyName: { [op.like]: `%${body.searchText}%` } }, { jobTitle: { [op.like]: `%${body.searchText}%` } }, { content: { [op.like]: `%${body.searchText}%` } } ]; } const result = await chat_records.findAndCountAll({ where, limit, offset, order: [['id', 'DESC']] }); return ctx.success({ count: result.count, total: result.count, rows: result.rows, list: result.rows }); }, /** * @swagger * /admin_api/chat/statistics: * get: * summary: 获取聊天统计 * description: 获取聊天记录的统计数据 * tags: [后台-聊天管理] * responses: * 200: * description: 获取成功 */ 'GET /chat/statistics': async (ctx) => { const models = Framework.getModels(); const { chat_records } = models; const [ totalChats, hasReplyCount, noReplyCount ] = await Promise.all([ chat_records.count(), chat_records.count({ where: { hasReply: true } }), chat_records.count({ where: { hasReply: false } }) ]); const typeStats = await chat_records.findAll({ attributes: [ 'chatType', [models.sequelize.fn('COUNT', models.sequelize.col('*')), 'count'] ], group: ['chatType'], raw: true }); const platformStats = await chat_records.findAll({ attributes: [ 'platform', [models.sequelize.fn('COUNT', models.sequelize.col('*')), 'count'] ], group: ['platform'], raw: true }); return ctx.success({ totalChats, hasReplyCount, noReplyCount, replyRate: totalChats > 0 ? ((hasReplyCount / totalChats) * 100).toFixed(2) : 0, typeStats, platformStats }); }, /** * @swagger * /admin_api/chat/detail: * get: * summary: 获取聊天记录详情 * description: 根据聊天ID获取详细信息 * tags: [后台-聊天管理] * parameters: * - in: query * name: chatId * required: true * schema: * type: string * description: 聊天记录ID * responses: * 200: * description: 获取成功 */ 'GET /chat/detail': async (ctx) => { const models = Framework.getModels(); const { chat_records } = models; const { chatId } = ctx.query; const body = ctx.getBody(); const id = chatId || body.chatId; if (!id) { return ctx.fail('聊天记录ID不能为空'); } const chat = await chat_records.findOne({ where: { id } }); if (!chat) { return ctx.fail('聊天记录不存在'); } return ctx.success(chat); }, /** * @swagger * /admin_api/chat/delete: * post: * summary: 删除聊天记录 * description: 删除指定的聊天记录 * tags: [后台-聊天管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - chatId * properties: * chatId: * type: string * description: 聊天记录ID * responses: * 200: * description: 删除成功 */ 'POST /chat/delete': async (ctx) => { const models = Framework.getModels(); const { chat_records } = models; const body = ctx.getBody(); const chatId = body.chatId || body.id; if (!chatId) { return ctx.fail('聊天记录ID不能为空'); } const result = await chat_records.destroy({ where: { id: chatId } }); if (result === 0) { return ctx.fail('聊天记录不存在'); } return ctx.success({ message: '聊天记录删除成功' }); }, /** * @swagger * /admin_api/chat/by-job: * get: * summary: 获取指定职位的聊天记录 * description: 根据职位ID和设备SN码获取聊天记录 * tags: [后台-聊天管理] * parameters: * - in: query * name: jobId * required: true * schema: * type: string * description: 职位ID * - in: query * name: sn_code * required: true * schema: * type: string * description: 设备SN码 * responses: * 200: * description: 获取成功 */ 'GET /chat/by-job': async (ctx) => { const models = Framework.getModels(); const { chat_records } = models; const { jobId, sn_code } = ctx.query; if (!jobId || !sn_code) { return ctx.fail('职位ID和设备SN码不能为空'); } const records = await chat_records.findAll({ where: { jobId, sn_code }, order: [['sendTime', 'ASC'], ['receiveTime', 'ASC'], ['id', 'ASC']] }); return ctx.success(records); }, /** * @swagger * /admin_api/chat/send: * post: * summary: 发送聊天消息 * description: 向指定职位发送聊天消息 * tags: [后台-聊天管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - sn_code * - jobId * - content * - platform * properties: * sn_code: * type: string * description: 设备SN码 * jobId: * type: string * description: 职位ID * content: * type: string * description: 消息内容 * chatType: * type: string * description: 聊天类型 * platform: * type: string * description: 平台(boss/liepin) * responses: * 200: * description: 发送成功 */ 'POST /chat/send': async (ctx) => { const models = Framework.getModels(); const { chat_records } = models; const body = ctx.getBody(); const { sn_code, jobId, content, chatType = 'reply', platform } = body; if (!sn_code || !jobId || !content || !platform) { return ctx.fail('设备SN码、职位ID、消息内容和平台不能为空'); } const chatRecord = await chat_records.create({ sn_code, jobId, platform, content, chatType, direction: 'sent', sendStatus: 'pending', sendTime: new Date() }); console.log(`[聊天管理] 消息待发送到设备 ${sn_code}:`, content); await chatRecord.update({ sendStatus: 'sent' }); return ctx.success({ message: '消息发送成功', chatRecord }); }, /** * @swagger * /admin_api/chat/unread-count: * get: * summary: 获取未读消息数量 * description: 获取指定设备的未读消息数量 * tags: [后台-聊天管理] * parameters: * - in: query * name: sn_code * required: true * schema: * type: string * description: 设备SN码 * responses: * 200: * description: 获取成功 */ 'GET /chat/unread-count': async (ctx) => { const models = Framework.getModels(); const { chat_records } = models; const { sn_code } = ctx.query; if (!sn_code) { return ctx.fail('设备SN码不能为空'); } const count = await chat_records.count({ where: { sn_code, direction: 'received', hasReply: false } }); return ctx.success({ unreadCount: count }); }, /** * @swagger * /admin_api/chat/mark-read: * post: * summary: 标记消息为已读 * description: 将指定的聊天消息标记为已读 * tags: [后台-聊天管理] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - chatId * properties: * chatId: * type: string * description: 聊天记录ID * responses: * 200: * description: 标记成功 */ 'POST /chat/mark-read': async (ctx) => { const models = Framework.getModels(); const { chat_records } = models; const body = ctx.getBody(); const chatId = body.chatId || body.id; if (!chatId) { return ctx.fail('聊天记录ID不能为空'); } const result = await chat_records.update( { hasReply: true, replyTime: new Date() }, { where: { id: chatId } } ); if (result[0] === 0) { return ctx.fail('聊天记录不存在'); } return ctx.success({ message: '标记成功' }); }, /** * @swagger * /admin_api/chat/export: * post: * summary: 导出聊天记录 * description: 导出聊天记录为CSV文件 * tags: [后台-聊天管理] * responses: * 200: * description: 导出成功 */ 'POST /chat/export': async (ctx) => { const models = Framework.getModels(); const { chat_records, op } = models; const body = ctx.getBody(); const where = {}; if (body.platform) where.platform = body.platform; if (body.chatType) where.chatType = body.chatType; if (body.hasReply !== undefined) where.hasReply = body.hasReply; const records = await chat_records.findAll({ where, order: [['id', 'DESC']], limit: 10000 }); const headers = ['ID', '设备SN码', '平台', '职位ID', '公司名称', '职位名称', 'HR姓名', '消息方向', '聊天类型', '消息内容', '发送时间', '接收时间', '是否有回复']; let csvContent = '\uFEFF' + headers.join(',') + '\n'; records.forEach(record => { const row = [ record.id || '', record.sn_code || '', record.platform || '', record.jobId || '', `"${(record.companyName || '').replace(/"/g, '""')}"`, `"${(record.jobTitle || '').replace(/"/g, '""')}"`, `"${(record.hrName || '').replace(/"/g, '""')}"`, record.direction || '', record.chatType || '', `"${(record.content || '').replace(/"/g, '""')}"`, record.sendTime || '', record.receiveTime || '', record.hasReply ? '是' : '否' ]; csvContent += row.join(',') + '\n'; }); ctx.set('Content-Type', 'text/csv;charset=utf-8'); ctx.set('Content-Disposition', `attachment; filename="chat_records_${Date.now()}.csv"`); ctx.body = csvContent; } };