Files
autoAiWorkSys/api/controller_admin/chat_records.js
张成 7ee92b8905 1
2025-12-25 22:11:34 +08:00

498 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({
rows: result.rows,
count: result.count
});
},
/**
* @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;
}
};