500 lines
14 KiB
JavaScript
500 lines
14 KiB
JavaScript
/**
|
|
* 聊天记录管理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;
|
|
}
|
|
};
|
|
|