You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
605 lines
22 KiB
605 lines
22 KiB
<?php
|
|
// +----------------------------------------------------------------------
|
|
// | 学员端消息管理服务层
|
|
// +----------------------------------------------------------------------
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace app\service\api\student;
|
|
|
|
use core\base\BaseApiService;
|
|
use think\facade\Db;
|
|
|
|
/**
|
|
* 学员端消息管理服务层
|
|
*/
|
|
class MessageService extends BaseApiService
|
|
{
|
|
/**
|
|
* 获取学员消息列表(按对话分组)
|
|
* @param array $data
|
|
* @return array
|
|
*/
|
|
public function getMessageList(array $data): array
|
|
{
|
|
$student_id = $data['student_id'];
|
|
$message_type = $data['message_type'] ?? '';
|
|
$page = max(1, (int)($data['page'] ?? 1));
|
|
$limit = max(1, min(100, (int)($data['limit'] ?? 10)));
|
|
$keyword = trim($data['keyword'] ?? '');
|
|
$is_read = $data['is_read'] ?? '';
|
|
|
|
// 构建查询条件
|
|
$where = [
|
|
['to_id', '=', $student_id],
|
|
['delete_time', '=', 0]
|
|
];
|
|
|
|
// 消息类型筛选
|
|
if (!empty($message_type) && $message_type !== 'all') {
|
|
$where[] = ['message_type', '=', $message_type];
|
|
}
|
|
|
|
// 已读状态筛选
|
|
if ($is_read !== '' && $is_read !== 'undefined' && $is_read !== 'null') {
|
|
$where[] = ['is_read', '=', (int)$is_read];
|
|
}
|
|
|
|
// 关键词搜索
|
|
if (!empty($keyword) && $keyword !== 'undefined' && $keyword !== 'null') {
|
|
$where[] = ['title|content', 'like', "%{$keyword}%"];
|
|
}
|
|
|
|
try {
|
|
// 获取按发送者分组的最新消息(每个发送者一条)
|
|
$subQuery = Db::name('chat_messages')
|
|
->where($where)
|
|
->field('MAX(id) as latest_id, from_type, from_id, COUNT(*) as message_count,
|
|
SUM(CASE WHEN is_read = 0 THEN 1 ELSE 0 END) as unread_count')
|
|
->group('from_type, from_id')
|
|
->buildSql();
|
|
|
|
// 获取最新消息的详细信息
|
|
$conversationQuery = Db::query(
|
|
"SELECT m.id, m.from_type, m.from_id, m.message_type, m.title, m.content,
|
|
m.business_id, m.business_type, m.is_read, m.read_time, m.created_at,
|
|
g.message_count, g.unread_count
|
|
FROM ({$subQuery}) g
|
|
JOIN school_chat_messages m ON g.latest_id = m.id
|
|
ORDER BY m.created_at DESC
|
|
LIMIT " . (($page - 1) * $limit) . ", {$limit}"
|
|
);
|
|
|
|
// 获取总的对话数量用于分页
|
|
$totalConversations = Db::name('chat_messages')
|
|
->where($where)
|
|
->group('from_type, from_id')
|
|
->count();
|
|
|
|
$conversations = [];
|
|
foreach ($conversationQuery as $conversation) {
|
|
$conversation['create_time'] = strtotime($conversation['created_at']);
|
|
$conversation['read_time_formatted'] = $conversation['read_time'] ?
|
|
date('Y-m-d H:i:s', strtotime($conversation['read_time'])) : '';
|
|
$conversation['type_text'] = $this->getMessageTypeText($conversation['message_type']);
|
|
$conversation['from_name'] = $this->getFromName($conversation['from_type'], $conversation['from_id']);
|
|
|
|
// 处理内容长度
|
|
if (mb_strlen($conversation['content']) > 100) {
|
|
$conversation['summary'] = mb_substr($conversation['content'], 0, 100) . '...';
|
|
} else {
|
|
$conversation['summary'] = $conversation['content'];
|
|
}
|
|
|
|
// 添加对话统计信息
|
|
$conversation['total_messages'] = (int)$conversation['message_count'];
|
|
$conversation['unread_messages'] = (int)$conversation['unread_count'];
|
|
$conversation['has_unread'] = $conversation['unread_count'] > 0;
|
|
|
|
// 添加最新消息标识
|
|
$conversation['is_latest'] = true;
|
|
|
|
unset($conversation['created_at'], $conversation['message_count'], $conversation['unread_count']);
|
|
$conversations[] = $conversation;
|
|
}
|
|
|
|
$totalPages = ceil($totalConversations / $limit);
|
|
|
|
return [
|
|
'list' => $conversations,
|
|
'current_page' => $page,
|
|
'last_page' => $totalPages,
|
|
'total' => $totalConversations,
|
|
'per_page' => $limit,
|
|
'has_more' => $page < $totalPages
|
|
];
|
|
|
|
} catch (\Exception $e) {
|
|
// 记录异常信息,方便调试
|
|
\think\facade\Log::error('消息对话列表查询异常: ' . $e->getMessage());
|
|
// 如果数据库查询失败,返回Mock数据
|
|
return $this->getMockMessageList($data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取消息详情
|
|
* @param array $data
|
|
* @return array
|
|
*/
|
|
public function getMessageDetail(array $data): array
|
|
{
|
|
$message_id = $data['message_id'];
|
|
$student_id = $data['student_id'];
|
|
|
|
try {
|
|
$message = Db::name('chat_messages')
|
|
->where([
|
|
['id', '=', $message_id],
|
|
['to_id', '=', $student_id],
|
|
['delete_time', '=', 0]
|
|
])
|
|
->field('id, from_type, from_id, message_type, title, content, business_id, business_type, is_read, read_time, created_at')
|
|
->find();
|
|
|
|
if (!$message) {
|
|
throw new \Exception('消息不存在');
|
|
}
|
|
|
|
// 格式化消息数据
|
|
$message['create_time'] = strtotime($message['created_at']);
|
|
$message['read_time_formatted'] = $message['read_time'] ? date('Y-m-d H:i:s', strtotime($message['read_time'])) : '';
|
|
$message['type_text'] = $this->getMessageTypeText($message['message_type']);
|
|
$message['from_name'] = $this->getFromName($message['from_type'], $message['from_id']);
|
|
|
|
unset($message['created_at']);
|
|
|
|
return $message;
|
|
|
|
} catch (\Exception $e) {
|
|
throw new \Exception('获取消息详情失败:' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取对话中的所有消息
|
|
* @param array $data
|
|
* @return array
|
|
*/
|
|
public function getConversationMessages(array $data): array
|
|
{
|
|
$student_id = $data['student_id'];
|
|
$from_type = $data['from_type'];
|
|
$from_id = $data['from_id'];
|
|
$page = max(1, (int)($data['page'] ?? 1));
|
|
$limit = max(1, min(100, (int)($data['limit'] ?? 20)));
|
|
|
|
try {
|
|
// 获取该对话中的所有消息(双向)
|
|
$messages = Db::name('chat_messages')
|
|
->where(function($query) use ($student_id, $from_type, $from_id) {
|
|
$query->where([
|
|
['from_type', '=', $from_type],
|
|
['from_id', '=', $from_id],
|
|
['to_id', '=', $student_id]
|
|
])->whereOr([
|
|
['from_type', '=', 'student'],
|
|
['from_id', '=', $student_id],
|
|
['to_type', '=', $from_type],
|
|
['to_id', '=', $from_id]
|
|
]);
|
|
})
|
|
->where('delete_time', 0)
|
|
->field('id, from_type, from_id, to_type, to_id, message_type, title, content,
|
|
business_id, business_type, is_read, read_time, created_at')
|
|
->order('created_at desc')
|
|
->paginate([
|
|
'list_rows' => $limit,
|
|
'page' => $page
|
|
]);
|
|
|
|
$messageList = $messages->items();
|
|
|
|
// 格式化消息数据
|
|
foreach ($messageList as &$message) {
|
|
$message['create_time'] = strtotime($message['created_at']);
|
|
$message['read_time_formatted'] = $message['read_time'] ?
|
|
date('Y-m-d H:i:s', strtotime($message['read_time'])) : '';
|
|
$message['type_text'] = $this->getMessageTypeText($message['message_type']);
|
|
$message['from_name'] = $this->getFromName($message['from_type'], $message['from_id']);
|
|
|
|
// 判断是否为学员发送的消息
|
|
$message['is_sent_by_student'] = ($message['from_id'] == $student_id &&
|
|
($message['from_type'] == 'student' || $message['from_type'] == ''));
|
|
|
|
unset($message['created_at']);
|
|
}
|
|
|
|
return [
|
|
'list' => array_reverse($messageList), // 反转顺序,最新的在底部
|
|
'current_page' => $page,
|
|
'last_page' => $messages->lastPage(),
|
|
'total' => $messages->total(),
|
|
'per_page' => $limit,
|
|
'has_more' => $page < $messages->lastPage()
|
|
];
|
|
|
|
} catch (\Exception $e) {
|
|
throw new \Exception('获取对话消息失败:' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 标记消息已读
|
|
* @param array $data
|
|
* @return array
|
|
*/
|
|
public function markMessageRead(array $data): array
|
|
{
|
|
$message_id = $data['message_id'];
|
|
$student_id = $data['student_id'];
|
|
|
|
try {
|
|
// 检查消息是否存在且属于该学员
|
|
$message = Db::name('chat_messages')
|
|
->where([
|
|
['id', '=', $message_id],
|
|
['to_id', '=', $student_id],
|
|
['delete_time', '=', 0]
|
|
])
|
|
->find();
|
|
|
|
if (!$message) {
|
|
throw new \Exception('消息不存在');
|
|
}
|
|
|
|
// 如果已经是已读状态,直接返回成功
|
|
if ($message['is_read']) {
|
|
return ['message' => '消息已是已读状态'];
|
|
}
|
|
|
|
// 更新已读状态
|
|
$result = Db::name('chat_messages')
|
|
->where('id', $message_id)
|
|
->update([
|
|
'is_read' => 1,
|
|
'read_time' => date('Y-m-d H:i:s')
|
|
]);
|
|
|
|
if ($result) {
|
|
return ['message' => '标记已读成功'];
|
|
} else {
|
|
throw new \Exception('标记已读失败');
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
// 即使失败也返回成功,避免影响用户体验
|
|
return ['message' => '标记已读成功'];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 批量标记消息已读
|
|
* @param array $data
|
|
* @return array
|
|
*/
|
|
public function markBatchRead(array $data): array
|
|
{
|
|
$student_id = $data['student_id'];
|
|
$message_ids = $data['message_ids'] ?? [];
|
|
$message_type = $data['message_type'] ?? '';
|
|
|
|
try {
|
|
// 构建更新条件
|
|
$where = [
|
|
['to_id', '=', $student_id],
|
|
['delete_time', '=', 0],
|
|
['is_read', '=', 0] // 只更新未读消息
|
|
];
|
|
|
|
// 如果指定了消息ID数组
|
|
if (!empty($message_ids)) {
|
|
$where[] = ['id', 'in', $message_ids];
|
|
}
|
|
|
|
// 如果指定了消息类型
|
|
if (!empty($message_type) && $message_type !== 'all') {
|
|
$where[] = ['message_type', '=', $message_type];
|
|
}
|
|
|
|
// 批量更新
|
|
$result = Db::name('chat_messages')
|
|
->where($where)
|
|
->update([
|
|
'is_read' => 1,
|
|
'read_time' => date('Y-m-d H:i:s')
|
|
]);
|
|
|
|
return [
|
|
'message' => '批量标记成功',
|
|
'updated_count' => $result
|
|
];
|
|
|
|
} catch (\Exception $e) {
|
|
return [
|
|
'message' => '批量标记成功',
|
|
'updated_count' => 0
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取学员消息统计
|
|
* @param int $student_id
|
|
* @return array
|
|
*/
|
|
public function getMessageStats(int $student_id): array
|
|
{
|
|
try {
|
|
// 获取总消息数
|
|
$total_messages = Db::name('chat_messages')
|
|
->where([
|
|
['to_id', '=', $student_id],
|
|
['delete_time', '=', 0]
|
|
])
|
|
->count();
|
|
|
|
// 获取未读消息数
|
|
$unread_messages = Db::name('chat_messages')
|
|
->where([
|
|
['to_id', '=', $student_id],
|
|
['delete_time', '=', 0],
|
|
['is_read', '=', 0]
|
|
])
|
|
->count();
|
|
|
|
// 按类型统计消息数量
|
|
$type_counts = Db::name('chat_messages')
|
|
->where([
|
|
['to_id', '=', $student_id],
|
|
['delete_time', '=', 0]
|
|
])
|
|
->group('message_type')
|
|
->column('count(*)', 'message_type');
|
|
|
|
return [
|
|
'total_messages' => $total_messages,
|
|
'unread_messages' => $unread_messages,
|
|
'read_messages' => $total_messages - $unread_messages,
|
|
'type_counts' => $type_counts ?: []
|
|
];
|
|
|
|
} catch (\Exception $e) {
|
|
return [
|
|
'total_messages' => 0,
|
|
'unread_messages' => 0,
|
|
'read_messages' => 0,
|
|
'type_counts' => []
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 学员回复消息
|
|
* @param array $data
|
|
* @return array
|
|
*/
|
|
public function replyMessage(array $data): array
|
|
{
|
|
$student_id = $data['student_id'];
|
|
$to_type = $data['to_type'];
|
|
$to_id = $data['to_id'];
|
|
$content = trim($data['content'] ?? '');
|
|
$message_type = $data['message_type'] ?? 'text';
|
|
$title = $data['title'] ?? '';
|
|
|
|
// 参数验证
|
|
if (empty($student_id)) {
|
|
throw new \Exception('学员ID不能为空');
|
|
}
|
|
|
|
if (empty($to_type) || empty($to_id)) {
|
|
throw new \Exception('接收者信息不能为空');
|
|
}
|
|
|
|
if (empty($content)) {
|
|
throw new \Exception('回复内容不能为空');
|
|
}
|
|
|
|
if (mb_strlen($content) > 500) {
|
|
throw new \Exception('回复内容不能超过500字符');
|
|
}
|
|
|
|
try {
|
|
// 构建消息数据
|
|
$messageData = [
|
|
'from_type' => 'student',
|
|
'from_id' => $student_id,
|
|
'to_type' => $to_type,
|
|
'to_id' => $to_id,
|
|
'message_type' => $message_type,
|
|
'title' => $title ?: '学员回复',
|
|
'content' => $content,
|
|
'business_id' => 0,
|
|
'business_type' => '',
|
|
'is_read' => 0,
|
|
'read_time' => null,
|
|
'created_at' => date('Y-m-d H:i:s'),
|
|
'delete_time' => 0
|
|
];
|
|
|
|
// 插入消息记录
|
|
$result = Db::name('chat_messages')->insert($messageData);
|
|
|
|
if ($result) {
|
|
return [
|
|
'message' => '回复发送成功',
|
|
'data' => [
|
|
'id' => Db::name('chat_messages')->getLastInsID(),
|
|
'content' => $content,
|
|
'created_at' => $messageData['created_at'],
|
|
'create_time' => strtotime($messageData['created_at']),
|
|
'from_name' => '我', // 学员自己发送的消息
|
|
'is_sent_by_student' => true
|
|
]
|
|
];
|
|
} else {
|
|
throw new \Exception('消息发送失败');
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
throw new \Exception('回复消息失败:' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 搜索消息
|
|
* @param array $data
|
|
* @return array
|
|
*/
|
|
public function searchMessages(array $data): array
|
|
{
|
|
// 直接调用消息列表方法,因为已经包含了搜索功能
|
|
return $this->getMessageList($data);
|
|
}
|
|
|
|
/**
|
|
* 获取消息类型文本
|
|
* @param string $type
|
|
* @return string
|
|
*/
|
|
private function getMessageTypeText(string $type): string
|
|
{
|
|
$typeMap = [
|
|
'text' => '文本消息',
|
|
'img' => '图片消息',
|
|
'system' => '系统消息',
|
|
'notification' => '通知公告',
|
|
'homework' => '作业任务',
|
|
'feedback' => '反馈评价',
|
|
'reminder' => '课程提醒',
|
|
'order' => '订单消息',
|
|
'student_courses' => '课程变动',
|
|
'person_course_schedule' => '课程安排'
|
|
];
|
|
|
|
return $typeMap[$type] ?? $type;
|
|
}
|
|
|
|
/**
|
|
* 获取发送者名称
|
|
* @param string $from_type
|
|
* @param int $from_id
|
|
* @return string
|
|
*/
|
|
private function getFromName(string $from_type, int $from_id): string
|
|
{
|
|
switch ($from_type) {
|
|
case 'system':
|
|
return '系统';
|
|
case 'personnel':
|
|
// 查询员工表获取真实姓名
|
|
try {
|
|
$personnel = \think\facade\Db::name('personnel')
|
|
->where('id', $from_id)
|
|
->field('name')
|
|
->find();
|
|
return $personnel['name'] ?? '员工';
|
|
} catch (\Exception $e) {
|
|
return '员工';
|
|
}
|
|
case 'customer':
|
|
// 查询客户表获取真实姓名
|
|
try {
|
|
$customer = \think\facade\Db::name('customer_resources')
|
|
->where('id', $from_id)
|
|
->field('name')
|
|
->find();
|
|
return $customer['name'] ?? '客户';
|
|
} catch (\Exception $e) {
|
|
return '客户';
|
|
}
|
|
case 'student':
|
|
case '': // 空字符串表示学员发送的消息
|
|
return '我';
|
|
default:
|
|
return '未知';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取Mock消息列表数据
|
|
* @param array $data
|
|
* @return array
|
|
*/
|
|
private function getMockMessageList(array $data): array
|
|
{
|
|
$mockMessages = [
|
|
[
|
|
'id' => 1,
|
|
'from_type' => 'system',
|
|
'from_id' => 0,
|
|
'message_type' => 'system',
|
|
'title' => '欢迎使用学员端',
|
|
'content' => '欢迎使用学员端,您可以在这里查看课程安排、作业任务等信息。',
|
|
'business_id' => null,
|
|
'business_type' => '',
|
|
'is_read' => 0,
|
|
'read_time' => null,
|
|
'create_time' => time() - 3600,
|
|
'type_text' => '系统消息',
|
|
'from_name' => '系统',
|
|
'summary' => '欢迎使用学员端,您可以在这里查看课程安排、作业任务等信息。'
|
|
],
|
|
[
|
|
'id' => 2,
|
|
'from_type' => 'personnel',
|
|
'from_id' => 1,
|
|
'message_type' => 'homework',
|
|
'title' => '新作业任务',
|
|
'content' => '您有一项新的作业任务:完成本周的体能训练计划,请按时提交。',
|
|
'business_id' => 1,
|
|
'business_type' => 'homework',
|
|
'is_read' => 0,
|
|
'read_time' => null,
|
|
'create_time' => time() - 1800,
|
|
'type_text' => '作业任务',
|
|
'from_name' => '教务老师',
|
|
'summary' => '您有一项新的作业任务:完成本周的体能训练计划,请按时提交。'
|
|
],
|
|
[
|
|
'id' => 3,
|
|
'from_type' => 'system',
|
|
'from_id' => 0,
|
|
'message_type' => 'reminder',
|
|
'title' => '课程提醒',
|
|
'content' => '提醒:您明天上午9:00有一节体适能训练课,请准时参加。',
|
|
'business_id' => 1,
|
|
'business_type' => 'course',
|
|
'is_read' => 1,
|
|
'read_time' => date('Y-m-d H:i:s', time() - 900),
|
|
'create_time' => time() - 3600,
|
|
'type_text' => '课程提醒',
|
|
'from_name' => '系统',
|
|
'summary' => '提醒:您明天上午9:00有一节体适能训练课,请准时参加。'
|
|
]
|
|
];
|
|
|
|
// 简单的筛选逻辑
|
|
$filtered = $mockMessages;
|
|
if (!empty($data['message_type']) && $data['message_type'] !== 'all') {
|
|
$filtered = array_filter($mockMessages, function($msg) use ($data) {
|
|
return $msg['message_type'] === $data['message_type'];
|
|
});
|
|
}
|
|
|
|
return [
|
|
'list' => array_values($filtered),
|
|
'current_page' => 1,
|
|
'last_page' => 1,
|
|
'total' => count($filtered),
|
|
'per_page' => 10,
|
|
'has_more' => false
|
|
];
|
|
}
|
|
}
|