1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-24 20:06:09 +08:00

设计即时通信结构

This commit is contained in:
xiaochong0302 2020-06-20 20:19:53 +08:00
parent 1dce158ee6
commit ceb3e1dcbb
16 changed files with 389 additions and 277 deletions

View File

@ -2,108 +2,25 @@
namespace App\Http\Web\Controllers;
use App\Http\Web\Services\Messenger as MessengerService;
use App\Traits\Response as ResponseTrait;
/**
* @RoutePrefix("/im")
*/
class MessengerController extends \Phalcon\Mvc\Controller
{
use ResponseTrait;
/**
* @Get("/init", name="im.init")
*/
public function initAction()
{
$data = [
'mine' => [
'id' => '100000', //我的ID
'username' => '纸飞机', //我的昵称
'sign' => '在深邃的编码世界,做一枚轻盈的纸飞机', //我的签名
'avatar' => '//wx2.sinaimg.cn/mw690/5db11ff4gy1flxmew7edlj203d03wt8n.jpg', //我的头像
'status' => 'online', //在线状态 online在线、hide隐身
],
'friend' => [
[
'id' => '1000',
'groupname' => '前端码农',
'online' => 3,
'list' => [
[
'id' => '1000',
'username' => '闲心',
'sign' => '我是如此的不寒而栗',
'avatar' => '//wx2.sinaimg.cn/mw690/5db11ff4gy1flxmew7edlj203d03wt8n.jpg', //我的头像
'status' => 'online',
],
[
'id' => '1001',
'username' => '妹儿美',
'sign' => '我是如此的不寒而栗',
'avatar' => '//wx2.sinaimg.cn/mw690/5db11ff4gy1flxmew7edlj203d03wt8n.jpg', //我的头像
'status' => 'online',
]
],
],
[
'id' => '1001',
'groupname' => '后端码农',
'online' => 2,
'list' => [
[
'id' => '1003',
'username' => '合肥马哥',
'sign' => '我是如此的不寒而栗',
'avatar' => '//wx2.sinaimg.cn/mw690/5db11ff4gy1flxmew7edlj203d03wt8n.jpg',
'status' => 'online',
],
[
'id' => '1004',
'username' => '合肥牛哥',
'sign' => '我是如此的不寒而栗',
'avatar' => '//wx2.sinaimg.cn/mw690/5db11ff4gy1flxmew7edlj203d03wt8n.jpg',
'status' => 'online',
]
],
],
[
'id' => '1002',
'groupname' => '全栈码农',
'online' => 1,
'list' => [
[
'id' => '1005',
'username' => '南拳',
'sign' => '我是如此的不寒而栗',
'avatar' => '//wx2.sinaimg.cn/mw690/5db11ff4gy1flxmew7edlj203d03wt8n.jpg',
'status' => 'online',
],
[
'id' => '1006',
'username' => '北腿',
'sign' => '我是如此的不寒而栗',
'avatar' => '//wx2.sinaimg.cn/mw690/5db11ff4gy1flxmew7edlj203d03wt8n.jpg',
'status' => 'online',
]
],
],
],
'group' => [
[
'id' => '1001',
'groupname' => '前端码农',
'avatar' => '//wx2.sinaimg.cn/mw690/5db11ff4gy1flxmew7edlj203d03wt8n.jpg',
],
[
'id' => '1002',
'groupname' => '后端码农',
'avatar' => '//wx2.sinaimg.cn/mw690/5db11ff4gy1flxmew7edlj203d03wt8n.jpg',
],
[
'id' => '1003',
'groupname' => '全栈码农',
'avatar' => '//wx2.sinaimg.cn/mw690/5db11ff4gy1flxmew7edlj203d03wt8n.jpg',
],
],
];
$service = new MessengerService();
$data = $service->init();
return $this->jsonSuccess(['data' => $data]);
}
@ -162,7 +79,11 @@ class MessengerController extends \Phalcon\Mvc\Controller
*/
public function bindUserAction()
{
$service = new MessengerService();
$service->bindUser();
return $this->jsonSuccess();
}
/**
@ -170,7 +91,11 @@ class MessengerController extends \Phalcon\Mvc\Controller
*/
public function sendMessageAction()
{
$service = new MessengerService();
$service->sendMessage();
return $this->jsonSuccess();
}
/**

View File

@ -2,34 +2,63 @@
namespace App\Http\Web\Services;
use App\Services\Frontend\ChapterTrait;
use App\Repos\User as UserRepo;
use App\Services\Frontend\UserTrait;
use GatewayClient\Gateway;
class Messenger extends Service
{
use ChapterTrait;
use UserTrait;
public function bindUser($id)
public function init()
{
$user = $this->getCurrentUser();
$user = $this->getLoginUser();
$userId = $user->id > 0 ?: $this->session->getId();
$mine = [
'id' => $user->id,
'username' => $user->name,
'sign' => $user->sign,
'avatar' => $user->avatar,
'status' => 'online',
];
$friend = $this->handleFriendList($user->id);
$group = $this->handleGroupList($user->id);
return [
'mine' => $mine,
'friend' => $friend,
'group' => $group,
];
}
public function bindUser()
{
$user = $this->getLoginUser();
$clientId = $this->request->getPost('client_id');
$groupName = $this->getGroupName($id);
Gateway::$registerAddress = '127.0.0.1:1238';
Gateway::bindUid($clientId, $userId);
Gateway::bindUid($clientId, $user->id);
$userRepo = new UserRepo();
$chatGroups = $userRepo->findImChatGroups($user->id);
if ($chatGroups->count() > 0) {
foreach ($chatGroups as $group) {
Gateway::joinGroup($clientId, $this->getGroupName($group->id));
}
}
Gateway::joinGroup($clientId, $groupName);
}
public function sendMessage($id)
public function sendMessage()
{
$chapter = $this->checkChapterCache($id);
$user = $this->getLoginUser();
$from = $this->request->getPost('from');
$to = $this->request->getPost('to');
@ -39,7 +68,7 @@ class Messenger extends Service
'avatar' => $from['avatar'],
'content' => $from['content'],
'fromid' => $from['id'],
'id' => $to['id'],
'id' => $from['id'],
'type' => $to['type'],
'timestamp' => 1000 * time(),
'mine' => false,
@ -50,11 +79,101 @@ class Messenger extends Service
'content' => $content,
]);
$groupName = $this->getGroupName($chapter->id);
Gateway::$registerAddress = '127.0.0.1:1238';
Gateway::sendToGroup($groupName, $message);
if ($to['type'] == 'friend') {
Gateway::sendToUid($to['id'], $message);
} elseif ($to['type'] == 'group') {
$excludeClientId = null;
if ($user->id == $from['id']) {
$excludeClientId = Gateway::getClientIdByUid($user->id);
}
$groupName = $this->getGroupName($to['id']);
Gateway::sendToGroup($groupName, $message, $excludeClientId);
}
}
protected function handleFriendList($userId)
{
$userRepo = new UserRepo();
$friendGroups = $userRepo->findImFriendGroups($userId);
$friends = $userRepo->findImFriends($userId);
$items = [];
$items[] = ['id' => 0, 'groupname' => '我的好友', 'list' => []];
if ($friendGroups->count() > 0) {
foreach ($friendGroups as $group) {
$items[] = ['id' => $group->id, 'groupname' => $group->name, 'online' => 0, 'list' => []];
}
}
if ($friends->count() == 0) {
return $items;
}
$userIds = kg_array_column($friends->toArray(), 'friend_id');
$users = $userRepo->findByIds($userIds);
$userMappings = [];
foreach ($users as $user) {
$userMappings[$user->id] = [
'id' => $user->id,
'username' => $user->name,
'avatar' => $user->avatar,
'sign' => $user->sign,
'status' => 'online',
];
}
foreach ($items as $key => $item) {
foreach ($friends as $friend) {
$userId = $friend->friend_id;
if ($item['id'] == $friend->group_id) {
$items[$key]['list'][] = $userMappings[$userId];
} else {
$items[0]['list'][] = $userMappings[$userId];
}
}
}
return $items;
}
protected function handleGroupList($userId)
{
$userRepo = new UserRepo();
$groups = $userRepo->findImChatGroups($userId);
if ($groups->count() == 0) {
return [];
}
$baseUrl = kg_ci_base_url();
$result = [];
foreach ($groups->toArray() as $group) {
$group['avatar'] = $baseUrl . $group['avatar'];
$result[] = [
'id' => $group['id'],
'groupname' => $group['name'],
'avatar' => $group['avatar'],
];
}
return $result;
}
protected function getGroupName($groupId)

View File

@ -51,14 +51,17 @@
{% block inline_js %}
<script>
if ($('#tab-courses').length > 0) {
var $tabCourses = $('#tab-courses');
helper.ajaxLoadHtml($tabCourses.attr('data-url'), $tabCourses.attr('id'));
}
if ($('#tab-users').length > 0) {
var $tabUsers = $('#tab-users');
helper.ajaxLoadHtml($tabUsers.attr('data-url'), $tabUsers.attr('id'));
}
layui.use(['jquery', 'element'], function () {
var $ = layui.jquery;
if ($('#tab-courses').length > 0) {
var $tabCourses = $('#tab-courses');
layui.ajaxLoadHtml($tabCourses.attr('data-url'), $tabCourses.attr('id'));
}
if ($('#tab-users').length > 0) {
var $tabUsers = $('#tab-users');
layui.ajaxLoadHtml($tabUsers.attr('data-url'), $tabUsers.attr('id'));
}
});
</script>
{% endblock %}

View File

@ -130,6 +130,26 @@ function kg_site_base_url()
return "{$scheme}://{$host}" . rtrim(dirname($path), '/');
}
/**
* 获取默认头像路径
*
* @return string
*/
function kg_default_avatar_path()
{
return '/img/avatar/default.png';
}
/**
* 获取默认封面路径
*
* @return string
*/
function kg_default_cover_path()
{
return '/img/cover/default.png';
}
/**
* 获取数据万象基准URL
*
@ -146,11 +166,9 @@ function kg_ci_base_url()
* 获取数据万象URL
*
* @param string $path
* @param int $width
* @param int $height
* @return string
*/
function kg_ci_img_url($path, $width = 0, $height = 0)
function kg_ci_img_url($path)
{
if (!$path) return '';
@ -160,37 +178,33 @@ function kg_ci_img_url($path, $width = 0, $height = 0)
$storage = new StorageService();
return $storage->getCiImageUrl($path, $width, $height);
return $storage->getCiImageUrl($path);
}
/**
* 获取头像数据万象URL
*
* @param string $path
* @param int $width
* @param int $height
* @return string
*/
function kg_ci_avatar_img_url($path, $width = 0, $height = 0)
function kg_ci_avatar_img_url($path)
{
$path = $path ?: '/img/avatar/default.png';
$path = $path ?: kg_default_avatar_path();
return kg_ci_img_url($path, $width, $height);
return kg_ci_img_url($path);
}
/**
* 获取封面数据万象URL
*
* @param string $path
* @param int $width
* @param int $height
* @return string
*/
function kg_ci_cover_img_url($path, $width = 0, $height = 0)
function kg_ci_cover_img_url($path)
{
$path = $path ?: '/img/cover/default.png';
$path = $path ?: kg_default_cover_path();
return kg_ci_img_url($path, $width, $height);
return kg_ci_img_url($path);
}
/**

View File

@ -278,6 +278,8 @@ class Course extends Model
if (Text::startsWith($this->cover, 'http')) {
$this->cover = self::getCoverPath($this->cover);
} elseif (empty($this->cover)) {
$this->cover = kg_default_cover_path();
}
if (!empty($attrs)) {

View File

@ -3,6 +3,7 @@
namespace App\Models;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
use Phalcon\Text;
class ImChatGroup extends Model
{
@ -90,6 +91,12 @@ class ImChatGroup extends Model
public function beforeCreate()
{
$this->create_time = time();
if (Text::startsWith($this->avatar, 'http')) {
$this->avatar = self::getAvatarPath($this->avatar);
} elseif (empty($this->avatar)) {
$this->avatar = kg_default_avatar_path();
}
}
public function beforeUpdate()
@ -97,5 +104,21 @@ class ImChatGroup extends Model
$this->update_time = time();
}
public function afterFetch()
{
if (!Text::startsWith($this->avatar, 'http')) {
$this->avatar = kg_ci_avatar_img_url($this->avatar);
}
}
public static function getAvatarPath($url)
{
if (Text::startsWith($url, 'http')) {
return parse_url($url, PHP_URL_PATH);
}
return $url;
}
}

View File

@ -26,6 +26,13 @@ class ImFriend extends Model
*/
public $friend_id;
/**
* 分组编号
*
* @var integer
*/
public $group_id;
/**
* 屏蔽标识
*

View File

@ -1,66 +0,0 @@
<?php
namespace App\Models;
class ImFriendGroupUser extends Model
{
/**
* 主键编号
*
* @var integer
*/
public $id;
/**
* 分组编号
*
* @var integer
*/
public $group_id;
/**
* 用户编号
*
* @var integer
*/
public $user_id;
/**
* 优先级
*
* @var integer
*/
public $priority;
/**
* 创建时间
*
* @var integer
*/
public $create_time;
/**
* 更新时间
*
* @var integer
*/
public $update_time;
public function getSource()
{
return 'kg_im_friend_group_user';
}
public function beforeCreate()
{
$this->create_time = time();
}
public function beforeUpdate()
{
$this->update_time = time();
}
}

View File

@ -0,0 +1,95 @@
<?php
namespace App\Models;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
class ImFriendMessage extends Model
{
/**
* 主键编号
*
* @var integer
*/
public $id;
/**
* 用户编号
*
* @var integer
*/
public $user_id;
/**
* 对话编号
*
* @var string
*/
public $chat_id;
/**
* 内容
*
* @var string
*/
public $content;
/**
* 删除标识
*
* @var integer
*/
public $deleted;
/**
* 创建时间
*
* @var integer
*/
public $create_time;
/**
* 更新时间
*
* @var integer
*/
public $update_time;
public function getSource()
{
return 'kg_im_friend_message';
}
public function initialize()
{
parent::initialize();
$this->addBehavior(
new SoftDelete([
'field' => 'deleted',
'value' => 1,
])
);
}
public function beforeCreate()
{
$this->create_time = time();
}
public function beforeUpdate()
{
$this->update_time = time();
}
public static function getChatId($aUserId, $bUserId)
{
$list = [$aUserId, $bUserId];
sort($list);
return implode('_', $list);
}
}

View File

@ -4,12 +4,9 @@ namespace App\Models;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
class ImMessage extends Model
class ImGroupMessage extends Model
{
const TYPE_FRIEND = 1; // 私聊
const TYPE_GROUP = 2; // 群聊
/**
* 主键编号
*
@ -18,32 +15,25 @@ class ImMessage extends Model
public $id;
/**
* 类型
* 群组编号
*
* @var integer
*/
public $type;
public $group_id;
/**
* 发送方编号
* 用户编号
*
* @var integer
*/
public $sender_id;
public $user_id;
/**
* 接收方编号
*
* @var integer
*/
public $receiver_id;
/**
* 内容编号
* 内容
*
* @var string
*/
public $content_id;
public $content;
/**
* 删除标识
@ -68,7 +58,7 @@ class ImMessage extends Model
public function getSource()
{
return 'kg_im_message';
return 'kg_im_group_message';
}
public function initialize()

View File

@ -1,27 +0,0 @@
<?php
namespace App\Models;
class ImMessageContent extends Model
{
/**
* 主键编号
*
* @var integer
*/
public $id;
/**
* 内容
*
* @var string
*/
public $content;
public function getSource()
{
return 'kg_im_message_content';
}
}

View File

@ -122,6 +122,8 @@ class Slide extends Model
if (Text::startsWith($this->cover, 'http')) {
$this->cover = self::getCoverPath($this->cover);
} elseif (empty($this->cover)) {
$this->cover = kg_default_cover_path();
}
if (is_array($this->style) && !empty($this->style)) {

View File

@ -193,6 +193,8 @@ class User extends Model
if (Text::startsWith($this->avatar, 'http')) {
$this->avatar = self::getAvatarPath($this->avatar);
} elseif (empty($this->avatar)) {
$this->avatar = kg_default_avatar_path();
}
}

View File

@ -3,8 +3,10 @@
namespace App\Repos;
use App\Library\Paginator\Adapter\QueryBuilder as PagerQueryBuilder;
use App\Models\ImChatGroup as ImGroupModel;
use App\Models\ImChatGroupUser as ImGroupUserModel;
use App\Models\ImChatGroup as ImChatGroupModel;
use App\Models\ImChatGroupUser as ImChatGroupUserModel;
use App\Models\ImFriend as ImFriendModel;
use App\Models\ImFriendGroup as ImFriendGroupModel;
use App\Models\User as UserModel;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset;
@ -113,17 +115,39 @@ class User extends Repository
->execute();
}
/**
* @param int $userId
* @return ResultsetInterface|Resultset|ImFriendModel[]
*/
public function findImFriends($userId)
{
return ImFriendModel::query()
->where('user_id = :user_id:', ['user_id' => $userId])
->execute();
}
public function findImGroups($userId)
/**
* @param int $userId
* @return ResultsetInterface|Resultset|ImFriendGroupModel[]
*/
public function findImFriendGroups($userId)
{
return ImFriendGroupModel::query()
->where('user_id = :user_id:', ['user_id' => $userId])
->andWhere('deleted = 0')
->execute();
}
/**
* @param int $userId
* @return ResultsetInterface|Resultset|ImChatGroupModel[]
*/
public function findImChatGroups($userId)
{
return $this->modelsManager->createBuilder()
->columns('g.*')
->addFrom(ImGroupModel::class, 'g')
->join(ImGroupUserModel::class, 'g.id = gu.user_id', 'gu')
->addFrom(ImChatGroupModel::class, 'g')
->join(ImChatGroupUserModel::class, 'g.id = gu.group_id', 'gu')
->where('gu.user_id = :user_id:', ['user_id' => $userId])
->andWhere('g.deleted = 0')
->getQuery()->execute();

View File

@ -2,18 +2,18 @@
namespace App\Services\Frontend\Messenger;
use App\Services\Frontend\CourseTrait;
use App\Services\Frontend\Service as FrontendService;
use App\Services\Frontend\UserTrait;
class ContactList extends FrontendService
{
use CourseTrait, UserTrait;
public function handle($id)
public function handle()
{
$user = $this->getLoginUser();
$mine = [
];
}
}

View File

@ -4,6 +4,30 @@ layui.use(['jquery', 'layim'], function () {
var layim = layui.layim;
var socket = new WebSocket('ws://127.0.0.1:8282');
socket.onopen = function () {
console.log('socket connect success');
};
socket.onclose = function () {
console.log('socket connect close');
};
socket.onerror = function () {
console.log('socket connect error');
};
socket.onmessage = function (e) {
var data = JSON.parse(e.data);
console.log(data);
if (data.type === 'ping') {
socket.send('pong...');
} else if (data.type === 'bind_user') {
bindUser(data.client_id);
} else if (data.type === 'show_message') {
showMessage(data.content);
}
};
layim.config({
title: '即时聊天',
init: {
@ -18,34 +42,12 @@ layui.use(['jquery', 'layim'], function () {
uploadFile: {
url: '/im/file/upload'
},
maxLength: 1500,
find: '/im/find',
msgbox: '/im/msg/box',
chatLog: '/im/chat/log'
});
layim.on('ready', function (options) {
socket.onopen = function () {
console.log('socket connect success');
};
socket.onclose = function () {
console.log('socket connect close');
};
socket.onerror = function () {
console.log('socket connect error');
};
socket.onmessage = function (e) {
var data = JSON.parse(e.data);
console.log(data);
if (data.type === 'ping') {
socket.send('pong...');
} else if (data.type === 'bind_user') {
bindUser(data.client_id);
} else if (data.type === 'show_message') {
showMessage(data.content);
}
};
});
layim.on('sendMessage', function (res) {
sendMessage(res.mine, res.to);
});
@ -61,16 +63,13 @@ layui.use(['jquery', 'layim'], function () {
function sendMessage(from, to) {
$.ajax({
type: 'POST',
dataType: 'json',
url: '/im/msg/send',
data: {from: from, to: to}
});
}
function showMessage(message) {
if (message.fromid !== user.id) {
layim.getMessage(message);
}
layim.getMessage(message);
}
});