智慧教务系统
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

<?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
];
}
}