This commit is contained in:
2025-11-18 09:37:05 +08:00
parent 61b2ccf106
commit a3e5568f93
27 changed files with 1055 additions and 46 deletions

View File

@@ -0,0 +1,67 @@
<?php
namespace app\chat\controller;
use app\chat\model\ChatCommonPhrase;
use app\common\controller\ApiController;
use support\Request;
use support\Response;
use support\think\Db;
class CommonPhraseController extends ApiController
{
/**
* 获取常用语列表
*/
public function index(Request $request): Response
{
$uid = $request->get('uid');
return $this->success(Db::name('chat_common_phrase')->where('user_id', $uid)
->order('sort', 'desc')
->field(['id', 'content', 'sort', 'created_time'])
->select());
}
/**
* 添加常用语
*/
public function store(Request $request): Response
{
$uid = $request->post('uid');
$content = $request->post('content', '');
$sort = $request->post('sort', 0);
if (empty($content) || mb_strlen($content) > 500) {
return $this->error('常用语内容不能为空且长度≤500字');
}
// 有重复的内容不给添加
$msg = Db::name('chat_common_phrase')->where(['user_id' => $uid, 'content' => $content])->find();
if($msg) {
return $this->error('此内容已经存在,不能重复添加');
}
$res = Db::name('chat_common_phrase')->insert([
'user_id' => $uid,
'content' => $content,
'sort' => $sort,
'created_time' => d(),
]);
if($res) {
return $this->success();
}else {
return $this->error();
}
}
/**
* 删除常用语
*/
public function destroy(Request $request, int $id): Response
{
$uid = $request->uid;
$phrase = ChatCommonPhrase::where(['id' => $id, 'user_id' => $uid])->find();
if (!$phrase) {
return json(['code' => 404, 'msg' => '常用语不存在']);
}
$phrase->delete();
return json(['code' => 200, 'msg' => '删除成功']);
}
}

View File

@@ -0,0 +1,418 @@
<?php
namespace app\chat\controller;
use app\chat\model\ChatGroup;
use app\chat\model\ChatGroupMember;
use app\chat\model\ChatGroupMute;
use app\chat\model\ChatDoNotDisturb;
use app\utils\Session;
use support\Request;
use support\Response;
class GroupController
{
/**
* 创建群(仅商家可创建)
*/
public function create(Request $request): Response
{
$uid = $request->uid;
$userType = $request->user_type;
if ($userType != 2) { // 2=商家
return json(['code' => 403, 'msg' => '仅商家可创建群聊']);
}
$name = $request->post('name', '');
$avatar = $request->post('avatar', '');
$announcement = $request->post('announcement', '');
$isPublic = $request->post('is_public', 0);
if (empty($name)) {
return json(['code' => 400, 'msg' => '群名称不能为空']);
}
$now = time();
// 创建群
$group = ChatGroup::create([
'name' => $name,
'avatar' => $avatar,
'owner_id' => $uid,
'announcement' => $announcement,
'is_public' => $isPublic,
'created_at' => $now,
'updated_at' => $now
]);
// 群主加入群
ChatGroupMember::create([
'group_id' => $group->id,
'user_id' => $uid,
'role' => 1, // 1=群主
'join_time' => $now,
'quit_time' => null,
'is_kicked' => 0
]);
return json([
'code' => 200,
'msg' => '群创建成功',
'data' => ['group_id' => $group->id]
]);
}
/**
* 加群
*/
public function join(Request $request): Response
{
$uid = $request->uid;
$groupId = $request->post('group_id', 0);
if (!$groupId) {
return json(['code' => 400, 'msg' => '缺少group_id']);
}
// 验证群是否存在
$group = ChatGroup::find($groupId);
if (!$group) {
return json(['code' => 404, 'msg' => '群不存在']);
}
// 验证是否已在群内
$exists = ChatGroupMember::where([
'group_id' => $groupId,
'user_id' => $uid,
'quit_time' => null,
'is_kicked' => 0
])->exists();
if ($exists) {
return json(['code' => 400, 'msg' => '已在群内']);
}
// 被踢用户不能重新加入(需群主邀请)
$isKicked = ChatGroupMember::where([
'group_id' => $groupId,
'user_id' => $uid,
'is_kicked' => 1
])->exists();
if ($isKicked) {
return json(['code' => 403, 'msg' => '你已被移出该群,无法重新加入']);
}
// 加入群
ChatGroupMember::create([
'group_id' => $groupId,
'user_id' => $uid,
'role' => 3, // 3=普通成员
'join_time' => time(),
'quit_time' => null,
'is_kicked' => 0
]);
return json(['code' => 200, 'msg' => '加群成功']);
}
/**
* 退群
*/
public function quit(Request $request): Response
{
$uid = $request->uid;
$groupId = $request->post('group_id', 0);
if (!$groupId) {
return json(['code' => 400, 'msg' => '缺少group_id']);
}
// 验证是否是群主(群主不能退群,需转让)
$isOwner = ChatGroupMember::where([
'group_id' => $groupId,
'user_id' => $uid,
'role' => 1,
'quit_time' => null
])->exists();
if ($isOwner) {
return json(['code' => 403, 'msg' => '群主不能退群,请先转让群主']);
}
// 标记退出时间
$member = ChatGroupMember::where([
'group_id' => $groupId,
'user_id' => $uid,
'quit_time' => null,
'is_kicked' => 0
])->find();
if (!$member) {
return json(['code' => 400, 'msg' => '不在群内']);
}
$member->quit_time = time();
$member->save();
return json(['code' => 200, 'msg' => '退群成功']);
}
/**
* 设置群公告(仅群主/管理员)
*/
public function setAnnouncement(Request $request): Response
{
$uid = $request->uid;
$groupId = $request->post('group_id', 0);
$announcement = $request->post('announcement', '');
if (!$groupId) {
return json(['code' => 400, 'msg' => '缺少group_id']);
}
// 验证权限
$role = ChatGroupMember::where([
'group_id' => $groupId,
'user_id' => $uid,
'quit_time' => null,
'is_kicked' => 0
])->value('role');
if (!in_array($role, [1, 2])) { // 1=群主2=管理员
return json(['code' => 403, 'msg' => '仅群主和管理员可设置群公告']);
}
// 更新公告
ChatGroup::where('id', $groupId)->update([
'announcement' => $announcement,
'updated_at' => time()
]);
return json(['code' => 200, 'msg' => '群公告设置成功']);
}
/**
* 设置群免打扰
*/
public function setDoNotDisturb(Request $request): Response
{
$uid = $request->uid;
$groupId = $request->post('group_id', 0);
$status = $request->post('status', 0); // 0=关闭1=开启
if (!$groupId) {
return json(['code' => 400, 'msg' => '缺少group_id']);
}
// 验证是否在群内
$isMember = ChatGroupMember::where([
'group_id' => $groupId,
'user_id' => $uid,
'quit_time' => null,
'is_kicked' => 0
])->exists();
if (!$isMember) {
return json(['code' => 400, 'msg' => '不在群内']);
}
// 生成会话ID
$sessionId = Session::generateSessionId(2, $uid, $groupId);
// 更新免打扰状态
ChatDoNotDisturb::updateOrCreate(
['user_id' => $uid, 'session_id' => $sessionId],
['status' => $status, 'updated_at' => time()]
);
return json(['code' => 200, 'msg' => $status ? '开启免打扰成功' : '关闭免打扰成功']);
}
/**
* 群成员禁言(仅群主/管理员)
*/
public function muteMember(Request $request): Response
{
$uid = $request->uid;
$groupId = $request->post('group_id', 0);
$targetUid = $request->post('target_uid', 0);
$muteTime = $request->post('mute_time', 3600); // 默认禁言1小时
if (!$groupId || !$targetUid) {
return json(['code' => 400, 'msg' => '缺少group_id或target_uid']);
}
// 验证操作人权限
$operatorRole = ChatGroupMember::where([
'group_id' => $groupId,
'user_id' => $uid,
'quit_time' => null,
'is_kicked' => 0
])->value('role');
if (!in_array($operatorRole, [1, 2])) {
return json(['code' => 403, 'msg' => '仅群主和管理员可禁言']);
}
// 验证被禁言用户是否在群内
$targetIsMember = ChatGroupMember::where([
'group_id' => $groupId,
'user_id' => $targetUid,
'quit_time' => null,
'is_kicked' => 0
])->exists();
if (!$targetIsMember) {
return json(['code' => 400, 'msg' => '被禁言用户不在群内']);
}
// 不能禁言群主
$groupOwner = ChatGroup::where('id', $groupId)->value('owner_id');
if ($targetUid == $groupOwner) {
return json(['code' => 403, 'msg' => '不能禁言群主']);
}
// 不能禁言自己
if ($targetUid == $uid) {
return json(['code' => 400, 'msg' => '不能禁言自己']);
}
$now = time();
$expireTime = $muteTime > 0 ? $now + $muteTime : null;
// 新增/更新禁言记录
ChatGroupMute::updateOrCreate(
['group_id' => $groupId, 'user_id' => $targetUid],
[
'mute_time' => $muteTime,
'expire_time' => $expireTime,
'operator_id' => $uid,
'created_at' => $now
]
);
return json(['code' => 200, 'msg' => "禁言成功,时长{$muteTime}"]);
}
/**
* 解除禁言(仅群主/管理员)
*/
public function unmuteMember(Request $request): Response
{
$uid = $request->uid;
$groupId = $request->post('group_id', 0);
$targetUid = $request->post('target_uid', 0);
if (!$groupId || !$targetUid) {
return json(['code' => 400, 'msg' => '缺少group_id或target_uid']);
}
// 验证操作人权限
$operatorRole = ChatGroupMember::where([
'group_id' => $groupId,
'user_id' => $uid,
'quit_time' => null,
'is_kicked' => 0
])->value('role');
if (!in_array($operatorRole, [1, 2])) {
return json(['code' => 403, 'msg' => '仅群主和管理员可解除禁言']);
}
// 删除禁言记录
ChatGroupMute::where([
'group_id' => $groupId,
'user_id' => $targetUid
])->delete();
return json(['code' => 200, 'msg' => '解除禁言成功']);
}
/**
* 踢人(仅群主/管理员)
*/
public function kickMember(Request $request): Response
{
$uid = $request->uid;
$groupId = $request->post('group_id', 0);
$targetUid = $request->post('target_uid', 0);
if (!$groupId || !$targetUid) {
return json(['code' => 400, 'msg' => '缺少group_id或target_uid']);
}
// 验证操作人权限
$operatorRole = ChatGroupMember::where([
'group_id' => $groupId,
'user_id' => $uid,
'quit_time' => null,
'is_kicked' => 0
])->value('role');
if (!in_array($operatorRole, [1, 2])) {
return json(['code' => 403, 'msg' => '仅群主和管理员可踢人']);
}
// 验证被踢用户是否在群内
$targetMember = ChatGroupMember::where([
'group_id' => $groupId,
'user_id' => $targetUid,
'quit_time' => null,
'is_kicked' => 0
])->find();
if (!$targetMember) {
return json(['code' => 400, 'msg' => '被踢用户不在群内']);
}
// 不能踢群主
$groupOwner = ChatGroup::where('id', $groupId)->value('owner_id');
if ($targetUid == $groupOwner) {
return json(['code' => 403, 'msg' => '不能踢群主']);
}
// 不能踢自己
if ($targetUid == $uid) {
return json(['code' => 400, 'msg' => '不能踢自己']);
}
// 标记为被踢
$targetMember->quit_time = time();
$targetMember->is_kicked = 1;
$targetMember->save();
// 删除被踢用户的禁言记录
ChatGroupMute::where([
'group_id' => $groupId,
'user_id' => $targetUid
])->delete();
return json(['code' => 200, 'msg' => '踢人成功']);
}
/**
* 获取群成员列表
*/
public function getMembers(Request $request): Response
{
$uid = $request->uid;
$groupId = $request->get('group_id', 0);
if (!$groupId) {
return json(['code' => 400, 'msg' => '缺少group_id']);
}
// 验证是否在群内
$isMember = ChatGroupMember::where([
'group_id' => $groupId,
'user_id' => $uid,
'quit_time' => null,
'is_kicked' => 0
])->exists();
if (!$isMember) {
return json(['code' => 403, 'msg' => '不在群内,无法获取成员列表']);
}
// 获取成员列表
$members = ChatGroupMember::where([
'group_id' => $groupId,
'quit_time' => null,
'is_kicked' => 0
])->join('chat_user', 'chat_group_member.user_id', 'chat_user.id')
->select([
'chat_group_member.user_id', 'chat_group_member.role',
'chat_user.username', 'chat_user.avatar', 'chat_user.type'
])->get()->toArray();
return json(['code' => 200, 'msg' => 'success', 'data' => $members]);
}
}

View File

@@ -0,0 +1,260 @@
<?php
namespace app\chat\controller;
use app\chat\model\ChatMessage;
use app\chat\model\ChatUnreadCount;
use app\chat\model\ChatTop;
use app\chat\model\ChatUser;
use app\chat\model\ChatGroup;
use app\utils\Session;
use support\Request;
use support\Response;
class MessageController
{
/**
* 获取历史消息
*/
public function history(Request $request): Response
{
$uid = $request->uid;
$chatType = $request->get('chat_type', 1); // 1=单聊2=群聊
$toId = $request->get('to_id', 0); // 单聊=对方ID群聊=群ID
$page = $request->get('page', 1);
$size = $request->get('size', 20);
if (!$toId) {
return json(['code' => 400, 'msg' => '缺少to_id']);
}
// 生成会话ID
$sessionId = Session::generateSessionId($chatType, $uid, $toId);
// 构建查询条件
$query = ChatMessage::where('session_id', $sessionId);
// 群聊需验证是否在群内
if ($chatType == 2) {
$isMember = app\chat\model\ChatGroupMember::where([
'group_id' => $toId,
'user_id' => $uid,
'quit_time' => null,
'is_kicked' => 0
])->exists();
if (!$isMember) {
return json(['code' => 403, 'msg' => '不在群内,无法获取历史消息']);
}
}
// 分页查询(倒序取,再正序返回)
$total = $query->count();
$messages = $query->orderBy('send_time', 'desc')
->page($page, $size)
->get()
->toArray();
$messages = array_reverse($messages);
// 单聊自动标记已读
if ($chatType == 1) {
ChatMessage::where([
'session_id' => $sessionId,
'to_id' => $uid,
'is_read' => 0
])->update(['is_read' => 1]);
// 重置未读计数
ChatUnreadCount::where([
'user_id' => $uid,
'session_id' => $sessionId
])->update(['count' => 0, 'updated_at' => time()]);
}
return json([
'code' => 200,
'msg' => 'success',
'data' => [
'list' => $messages,
'page' => $page,
'size' => $size,
'total' => $total
]
]);
}
/**
* 标记消息已读(批量)
*/
public function markRead(Request $request): Response
{
$uid = $request->uid;
$msgIds = $request->post('msg_ids', []);
if (empty($msgIds) || !is_array($msgIds)) {
return json(['code' => 400, 'msg' => '请传入有效消息ID数组']);
}
// 标记已读(仅自己接收的消息)
$messages = ChatMessage::whereIn('id', $msgIds)
->where('to_id', $uid)
->where('is_read', 0)
->get();
if (empty($messages)) {
return json(['code' => 200, 'msg' => '无未读消息可标记']);
}
// 批量更新
$sessionIds = $messages->pluck('session_id')->unique()->toArray();
ChatMessage::whereIn('id', $msgIds)->where('to_id', $uid)->update(['is_read' => 1]);
// 更新未读计数
foreach ($sessionIds as $sessionId) {
$reduceNum = ChatMessage::whereIn('id', $msgIds)
->where('session_id', $sessionId)
->count();
$unread = ChatUnreadCount::where(['user_id' => $uid, 'session_id' => $sessionId])->first();
if ($unread) {
$newCount = max(0, $unread->count - $reduceNum);
$unread->count = $newCount;
$unread->updated_at = time();
$unread->save();
}
}
return json(['code' => 200, 'msg' => '标记已读成功']);
}
/**
* 标记会话全部已读
*/
public function markReadAll(Request $request): Response
{
$uid = $request->uid;
$sessionId = $request->post('session_id', '');
if (empty($sessionId)) {
return json(['code' => 400, 'msg' => '缺少session_id']);
}
// 标记该会话所有未读消息已读
ChatMessage::where([
'session_id' => $sessionId,
'to_id' => $uid,
'is_read' => 0
])->update(['is_read' => 1]);
// 重置未读计数
ChatUnreadCount::where([
'user_id' => $uid,
'session_id' => $sessionId
])->update(['count' => 0, 'updated_at' => time()]);
return json(['code' => 200, 'msg' => '标记全部已读成功']);
}
/**
* 获取未读消息总数
*/
public function getUnreadCount(Request $request): Response
{
$uid = $request->uid;
$total = ChatUnreadCount::where('user_id', $uid)->sum('count');
return json(['code' => 200, 'msg' => 'success', 'data' => ['total' => $total]]);
}
/**
* 获取会话列表(含置顶、未读、最后消息)
*/
public function getSessionList(Request $request): Response
{
$uid = $request->uid;
// 获取所有会话ID
$sessionIds = ChatMessage::where(function ($query) use ($uid) {
$query->where('from_id', $uid)->orWhere('to_id', $uid);
})->groupBy('session_id')->pluck('session_id')->toArray();
if (empty($sessionIds)) {
return json(['code' => 200, 'msg' => 'success', 'data' => ['list' => []]]);
}
$sessionList = [];
foreach ($sessionIds as $sessionId) {
// 置顶状态
$top = ChatTop::where(['user_id' => $uid, 'session_id' => $sessionId])->first();
$isTop = $top && $top->status == 1 ? 1 : 0;
$sort = $top ? $top->sort : 0;
// 未读计数
$unread = ChatUnreadCount::where(['user_id' => $uid, 'session_id' => $sessionId])->first();
$unreadCount = $unread ? $unread->count : 0;
// 最后一条消息
$lastMsg = ChatMessage::where('session_id', $sessionId)
->orderBy('send_time', 'desc')
->first([
'id', 'from_id', 'msg_type', 'content', 'image_url',
'order_id', 'send_time', 'is_read'
]);
if (!$lastMsg) continue;
// 会话类型(单聊/群聊)
$chatType = strpos($sessionId, 'group_') === 0 ? 2 : 1;
$targetInfo = [];
if ($chatType == 1) {
// 单聊解析对方ID
list($minId, $maxId) = explode('_', $sessionId);
$targetUid = $uid == $minId ? $maxId : $minId;
$targetUser = ChatUser::find($targetUid);
if ($targetUser) {
$targetInfo = [
'id' => $targetUser->id,
'name' => $targetUser->username,
'avatar' => $targetUser->avatar,
'type' => $targetUser->type
];
}
} else {
// 群聊解析群ID
$groupId = str_replace('group_', '', $sessionId);
$group = ChatGroup::find($groupId);
if ($group) {
$targetInfo = [
'id' => $group->id,
'name' => $group->name,
'avatar' => $group->avatar,
'owner_id' => $group->owner_id
];
}
}
$sessionList[] = [
'session_id' => $sessionId,
'chat_type' => $chatType,
'is_top' => $isTop,
'sort' => $sort,
'unread_count' => $unreadCount,
'last_msg' => $lastMsg ? $lastMsg->toArray() : [],
'target_info' => $targetInfo
];
}
// 排序置顶按sort降序→ 非置顶(按最后消息时间降序)
usort($sessionList, function ($a, $b) {
if ($a['is_top'] != $b['is_top']) {
return $b['is_top'] - $a['is_top'];
}
if ($a['is_top'] == 1) {
return $b['sort'] - $a['sort'];
}
$aTime = $a['last_msg']['send_time'] ?? 0;
$bTime = $b['last_msg']['send_time'] ?? 0;
return $bTime - $aTime;
});
return json(['code' => 200, 'msg' => 'success', 'data' => ['list' => $sessionList]]);
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace app\chat\controller;
use support\Request;
use support\Response;
use support\File;
class UploadController
{
/**
* 图片上传(相册/拍照)
*/
public function image(Request $request): Response
{
$file = $request->file('image');
if (!$file || !$file->isValid()) {
return json(['code' => 400, 'msg' => '图片上传失败']);
}
// 校验配置
$uploadConfig = config('process')['chat-gateway']['options']['upload'] ?? [];
$maxSize = $uploadConfig['image_size'] ?? 5 * 1024 * 1024;
$allowTypes = $uploadConfig['image_type'] ?? ['image/jpeg', 'image/png', 'image/gif'];
// 校验类型和大小
if (!in_array($file->getMimeType(), $allowTypes)) {
return json(['code' => 400, 'msg' => '仅支持JPG、PNG、GIF格式']);
}
if ($file->getSize() > $maxSize) {
return json(['code' => 400, 'msg' => "图片大小不能超过" . ($maxSize / 1024 / 1024) . "MB"]);
}
// 保存路径
$saveDir = public_path() . '/uploads/images/' . date('Ymd');
if (!is_dir($saveDir)) {
mkdir($saveDir, 0755, true);
}
// 生成文件名
$ext = $file->getExtension();
$filename = uniqid() . '.' . $ext;
$savePath = $saveDir . '/' . $filename;
// 移动文件
$file->moveTo($savePath);
// 访问URL
$imageUrl = '/uploads/images/' . date('Ymd') . '/' . $filename;
return json([
'code' => 200,
'msg' => '上传成功',
'data' => ['image_url' => $imageUrl]
]);
}
}