即时消息接口完结
This commit is contained in:
@@ -95,6 +95,14 @@ class GroupController extends ApiController
|
||||
return $this->error('已在群内');
|
||||
}
|
||||
|
||||
|
||||
// 再次加入时
|
||||
$z_exists = Db::name('chat_group_member')->where([
|
||||
'group_id' => $group_id,
|
||||
'user_id' => $this->uid,
|
||||
'is_kicked' => 0
|
||||
])->where('quit_time', 'not null')->find();
|
||||
|
||||
// 被踢用户不能重新加入(需群主邀请)
|
||||
$isKicked = Db::name('chat_group_member')->where([
|
||||
'group_id' => $group_id,
|
||||
@@ -113,6 +121,8 @@ class GroupController extends ApiController
|
||||
'join_time' => d(),
|
||||
];
|
||||
$is_insert = true;
|
||||
|
||||
|
||||
if($invite) {
|
||||
// 验证邀请参数是否正常
|
||||
$decryptedText = simple_decrypt($invite, config('cons.sercer_key'));
|
||||
@@ -125,6 +135,11 @@ class GroupController extends ApiController
|
||||
$insert_arr['group_id'] = $decryptedText_arr['group_id'];
|
||||
$group_id = $decryptedText_arr['group_id'];
|
||||
}
|
||||
// 如果是再次加入
|
||||
if($z_exists) {
|
||||
$insert_arr['quit_time'] = null;
|
||||
$is_insert = false;
|
||||
}
|
||||
try {
|
||||
Db::startTrans();
|
||||
// 插入用户信息
|
||||
@@ -258,6 +273,9 @@ class GroupController extends ApiController
|
||||
'quit_time' => null,
|
||||
'role' => 1
|
||||
])->update(['user_id' => $object_user_id, 'object_user_id' => $this->uid]);
|
||||
Db::name('chat_group')->where([
|
||||
'id' => $group_id,
|
||||
])->update(['owner_id' => $object_user_id]);
|
||||
if($res) {
|
||||
return $this->success();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use app\chat\model\ChatUser;
|
||||
use app\chat\model\ChatGroup;
|
||||
use app\common\controller\ApiController;
|
||||
use app\utils\Session;
|
||||
use support\Redis;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use support\think\Db;
|
||||
@@ -48,10 +49,11 @@ class MessageController extends ApiController
|
||||
|
||||
// 分页查询(倒序取,再正序返回)
|
||||
$total = $query->count();
|
||||
$messages = $query->order('send_time', 'desc')
|
||||
$messages = $query->alias('msg')->order('send_time', 'desc')
|
||||
->leftJoin('chat_user user', 'msg.from_id = user.user_id')
|
||||
->field('msg.id,from_id,to_id,chat_type,msg_type,content,image_url,order_id,is_read,send_time,session_id,nick_name,user_id,avatar,type')
|
||||
->page($page, $size)
|
||||
->select()->toArray();
|
||||
$messages = array_reverse($messages);
|
||||
|
||||
// 单聊自动标记已读
|
||||
if ($chatType == 1) {
|
||||
@@ -67,7 +69,9 @@ class MessageController extends ApiController
|
||||
'session_id' => $session_id
|
||||
])->update(['count' => 0, 'updated_time' => d()]);
|
||||
}
|
||||
|
||||
foreach ($messages as $k => &$v) {
|
||||
$v['send_time'] = formatWeChatTime($v['send_time']);
|
||||
}
|
||||
return $this->success([
|
||||
'list' => $messages,
|
||||
'page' => $page,
|
||||
@@ -80,42 +84,33 @@ class MessageController extends ApiController
|
||||
*/
|
||||
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数组']);
|
||||
$msgIds = $request->post('msg_ids');
|
||||
if (empty($msgIds) || !is_string($msgIds)) {
|
||||
return $this->error('请传入有效消息ID');
|
||||
}
|
||||
|
||||
$msgIds = explode(',', $msgIds);
|
||||
$query = Db::name('chat_message')->whereIn('id', $msgIds)->where('to_id', $this->uid)
|
||||
->where('is_read', 0);
|
||||
// 标记已读(仅自己接收的消息)
|
||||
$messages = ChatMessage::whereIn('id', $msgIds)
|
||||
->where('to_id', $uid)
|
||||
->where('is_read', 0)
|
||||
->get();
|
||||
|
||||
if (empty($messages)) {
|
||||
return json(['code' => 200, 'msg' => '无未读消息可标记']);
|
||||
$count = $query->count();
|
||||
if (empty($count)) {
|
||||
return $this->error('无未读消息可标记');
|
||||
}
|
||||
|
||||
// 批量更新
|
||||
$sessionIds = $messages->pluck('session_id')->unique()->toArray();
|
||||
ChatMessage::whereIn('id', $msgIds)->where('to_id', $uid)->update(['is_read' => 1]);
|
||||
|
||||
try {
|
||||
Db::startTrans();
|
||||
$sessionIds = $query->group('session_id')->column('session_id');
|
||||
// 更新未读计数
|
||||
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();
|
||||
Db::name('chat_unread_count')->where(['user_id' => $this->uid, 'session_id' => $sessionId])->update(['count' => 0, 'updated_time' => d()]);
|
||||
}
|
||||
// 批量更新
|
||||
$query->update(['is_read' => 1]);
|
||||
Db::commit();
|
||||
return $this->success();
|
||||
}catch (\Throwable $exception) {
|
||||
Db::rollback();
|
||||
return $this->error($exception->getMessage());
|
||||
}
|
||||
|
||||
return json(['code' => 200, 'msg' => '标记已读成功']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,27 +118,27 @@ class MessageController extends ApiController
|
||||
*/
|
||||
public function markReadAll(Request $request): Response
|
||||
{
|
||||
$uid = $request->uid;
|
||||
$sessionId = $request->post('session_id', '');
|
||||
|
||||
if (empty($sessionId)) {
|
||||
return json(['code' => 400, 'msg' => '缺少session_id']);
|
||||
$msgIds = $request->post('session_ids');
|
||||
if (empty($msgIds) || !is_string($msgIds)) {
|
||||
return $this->error('请传入有效会话ID');
|
||||
}
|
||||
$msgIds = explode(',', $msgIds);
|
||||
$query = Db::name('chat_message')->whereIn('session_id', $msgIds)->where('to_id', $this->uid)
|
||||
->where('is_read', 0);
|
||||
try {
|
||||
Db::startTrans();
|
||||
// 更新未读计数
|
||||
foreach ($msgIds as $sessionId) {
|
||||
Db::name('chat_unread_count')->where(['user_id' => $this->uid, 'session_id' => $sessionId])->update(['count' => 0, 'updated_time' => d()]);
|
||||
}
|
||||
// 批量更新
|
||||
$query->update(['is_read' => 1]);
|
||||
Db::commit();
|
||||
return $this->success();
|
||||
}catch (\Throwable $exception) {
|
||||
Db::rollback();
|
||||
return $this->error($exception->getMessage());
|
||||
}
|
||||
|
||||
// 标记该会话所有未读消息已读
|
||||
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' => '标记全部已读成功']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,10 +146,8 @@ class MessageController extends ApiController
|
||||
*/
|
||||
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]]);
|
||||
$total = Db::name('chat_unread_count')->where('user_id', $this->uid)->sum('count');
|
||||
return $this->success(['total' => $total]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,93 +155,17 @@ class MessageController extends ApiController
|
||||
*/
|
||||
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();
|
||||
|
||||
$sessionIds = Db::name('chat_message')->where('from_id', $this->uid)->WhereOr('to_id', $this->uid)
|
||||
->group('session_id')->column('session_id');
|
||||
if (empty($sessionIds)) {
|
||||
return json(['code' => 200, 'msg' => 'success', 'data' => ['list' => []]]);
|
||||
return $this->success();
|
||||
}
|
||||
|
||||
$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
|
||||
];
|
||||
$list = Redis::get('usermsg:list:' . $this->user_type . ':' . $this->uid);
|
||||
if($list) {
|
||||
return $this->success(['list' => json_decode($list, true)]);
|
||||
}else {
|
||||
return $this->success(['list' => ChatMessage::getconverlist($this->uid, $this->user_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]]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,51 @@
|
||||
namespace app\chat\model;
|
||||
|
||||
|
||||
use support\Redis;
|
||||
use support\think\Db;
|
||||
|
||||
class ChatMessage extends BaseModel
|
||||
{
|
||||
public $tabla_name = 'chat_message';
|
||||
|
||||
public static function getconverlist($uid, $user_type)
|
||||
{
|
||||
// 获取所有会话ID
|
||||
$sessionIds = Db::name('chat_message')->where('from_id', $uid)->WhereOr('to_id', $uid)
|
||||
->group('session_id')->column('session_id');
|
||||
$data = [];
|
||||
$i = 0;
|
||||
foreach ($sessionIds as $k => $session_id) {
|
||||
// 未读计数
|
||||
$unreadCount = Db::name('chat_unread_count')->where(['user_id' => $uid, 'session_id' => $session_id])->value('count')?:0;
|
||||
$data[$i]['unread_count'] = $unreadCount;
|
||||
// 最后一条消息
|
||||
$lastMsg = Db::name('chat_message')
|
||||
->where('session_id', $session_id)
|
||||
->order('send_time', 'desc')
|
||||
->find();
|
||||
if($lastMsg) {
|
||||
$chat_user = Db::name('chat_user')->where(['user_id' => $lastMsg['from_id']])->field('nick_name,avatar')->find();
|
||||
// 如果是自己发的不显示发送人
|
||||
if($lastMsg['from_id'] == $uid) {
|
||||
$data[$i]['msg'] = $lastMsg['content'];
|
||||
}else {
|
||||
// 如果是别人发的显示昵称
|
||||
$data[$i]['msg'] = $chat_user['nick_name'] . ':' . $lastMsg['content'];
|
||||
}
|
||||
$data[$i]['avatar'] = $chat_user['avatar'];
|
||||
$data[$i]['chat_type'] = $lastMsg['chat_type'];
|
||||
}else {
|
||||
$data[$i][] = '';
|
||||
}
|
||||
$data[$i]['send_time'] = formatWeChatTime($lastMsg['send_time']);
|
||||
$data[$i]['session_id'] = $session_id;
|
||||
$i ++;
|
||||
}
|
||||
if(Redis::set('usermsg:list:' . $user_type . ':' . $uid, json_encode($data))) {
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -141,16 +141,60 @@ function simple_decrypt(string $ciphertext, string $key): string|false
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 微信风格时间格式化(仅用原生 date/strtotime 函数,周一为一周起点)
|
||||
* @param string $inputTime 输入时间字符串(支持:Y-m-d H:i:s、timestamp、Y-m-d 等)
|
||||
* @param string $timezone 时区(默认北京时间,原生函数依赖服务器时区,建议提前配置)
|
||||
* @return string 格式化结果(如:09:11、昨天 15:30、周一 10:20、11-16 08:05、2024-05-20 14:30)
|
||||
*/
|
||||
function formatWeChatTime(string $inputTime, string $timezone = 'Asia/Shanghai'): string
|
||||
{
|
||||
// 1. 配置时区(原生函数需手动设置,避免服务器时区差异)
|
||||
date_default_timezone_set($timezone);
|
||||
|
||||
// 2. 转换目标时间为时间戳(兼容多种输入格式)
|
||||
$targetTimestamp = strtotime($inputTime);
|
||||
if ($targetTimestamp === false) {
|
||||
error_log('无效时间格式:' . $inputTime);
|
||||
return $inputTime; // 无效时间返回原始值
|
||||
}
|
||||
|
||||
// 3. 计算关键时间边界的时间戳(原生函数核心)
|
||||
$nowTimestamp = time(); // 当前时间戳
|
||||
$todayStart = strtotime('today'); // 今天 00:00:00(等价于 mktime(0,0,0,date('m'),date('d'),date('Y')))
|
||||
$yesterdayStart = strtotime('yesterday'); // 昨天 00:00:00(today - 86400)
|
||||
$currentWeekDay = date('N', $todayStart); // 今天是本周第几天(1=周一,7=周日)
|
||||
$mondayStart = strtotime("-$currentWeekDay days +1 day", $todayStart); // 本周一 00:00:00
|
||||
$yearStart = strtotime(date('Y') . '-01-01 00:00:00'); // 今年 1月1日 00:00:00
|
||||
|
||||
// 4. 周几映射(date('N') 返回 1-7 → 对应周一-周日)
|
||||
$weekMap = [1 => '周一', 2 => '周二', 3 => '周三', 4 => '周四', 5 => '周五', 6 => '周六', 7 => '周日'];
|
||||
|
||||
// 5. 按优先级判断场景(从近到远)
|
||||
// 场景1:今天(>= 今天0点)
|
||||
if ($targetTimestamp >= $todayStart) {
|
||||
return date('H:i', $targetTimestamp);
|
||||
}
|
||||
|
||||
// 场景2:昨天(>= 昨天0点 且 < 今天0点)
|
||||
if ($targetTimestamp >= $yesterdayStart && $targetTimestamp < $todayStart) {
|
||||
return '昨天 ' . date('H:i', $targetTimestamp);
|
||||
}
|
||||
|
||||
// 场景3:本周内(>= 本周一0点 且 < 今天0点)→ 显示周几+时间
|
||||
if ($targetTimestamp >= $mondayStart && $targetTimestamp < $todayStart) {
|
||||
$targetWeekDay = date('N', $targetTimestamp); // 目标时间的周几
|
||||
return $weekMap[$targetWeekDay] . ' ' . date('H:i', $targetTimestamp);
|
||||
}
|
||||
|
||||
// 场景4:今年内(>= 今年1月1日 且 < 本周一0点)→ 显示月-日+时间
|
||||
if ($targetTimestamp >= $yearStart && $targetTimestamp < $mondayStart) {
|
||||
return date('m-d H:i', $targetTimestamp);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 场景5:去年及以前(< 今年1月1日)→ 显示完整日期+时间
|
||||
return date('Y-m-d H:i', $targetTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多维数组去重并重新索引
|
||||
|
||||
@@ -48,9 +48,6 @@ Route::group('/api/chat', function () {
|
||||
Route::post('/unread-count', app\chat\controller\MessageController::class . '@getUnreadCount'); // 未读总数
|
||||
Route::post('/session-list', app\chat\controller\MessageController::class . '@getSessionList'); // 会话列表
|
||||
})->middleware($jwt_middleware);
|
||||
|
||||
// 图片上传(相册/拍照)
|
||||
Route::post('/upload/image', app\chat\controller\UploadController::class . '@image')->middleware($jwt_middleware);
|
||||
});
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user