diff --git a/app/Builders/ImMessageList.php b/app/Builders/ImMessageList.php
new file mode 100644
index 00000000..f71b7fc1
--- /dev/null
+++ b/app/Builders/ImMessageList.php
@@ -0,0 +1,41 @@
+getUsers($messages);
+
+ foreach ($messages as $key => $message) {
+ $messages[$key]['user'] = $users[$message['user_id']] ?? new \stdClass();
+ }
+
+ return $messages;
+ }
+
+ public function getUsers(array $messages)
+ {
+ $ids = kg_array_column($messages, 'user_id');
+
+ $userRepo = new UserRepo();
+
+ $users = $userRepo->findByIds($ids, ['id', 'name', 'avatar']);
+
+ $baseUrl = kg_ci_base_url();
+
+ $result = [];
+
+ foreach ($users->toArray() as $user) {
+ $user['avatar'] = $baseUrl . $user['avatar'];
+ $result[$user['id']] = $user;
+ }
+
+ return $result;
+ }
+
+}
diff --git a/app/Caches/ImChatGroup.php b/app/Caches/ImChatGroup.php
new file mode 100644
index 00000000..780b4ed4
--- /dev/null
+++ b/app/Caches/ImChatGroup.php
@@ -0,0 +1,31 @@
+lifetime;
+ }
+
+ public function getKey($id = null)
+ {
+ return "im_chat_group:{$id}";
+ }
+
+ public function getContent($id = null)
+ {
+ $groupRepo = new ImChatGroupRepo();
+
+ $group = $groupRepo->findById($id);
+
+ return $group ?: null;
+ }
+
+}
diff --git a/app/Caches/MaxImChatGroupId.php b/app/Caches/MaxImChatGroupId.php
new file mode 100644
index 00000000..9502f7d9
--- /dev/null
+++ b/app/Caches/MaxImChatGroupId.php
@@ -0,0 +1,29 @@
+lifetime;
+ }
+
+ public function getKey($id = null)
+ {
+ return 'max_im_chat_group_id';
+ }
+
+ public function getContent($id = null)
+ {
+ $group = ImChatGroupModel::findFirst(['order' => 'id DESC']);
+
+ return $group->id ?? 0;
+ }
+
+}
diff --git a/app/Http/Web/Controllers/LayerController.php b/app/Http/Web/Controllers/LayerController.php
new file mode 100644
index 00000000..e0568fd9
--- /dev/null
+++ b/app/Http/Web/Controllers/LayerController.php
@@ -0,0 +1,50 @@
+isNotSafeRequest()) {
+ $this->checkHttpReferer();
+ $this->checkCsrfToken();
+ }
+
+ $this->checkRateLimit();
+
+ return true;
+ }
+
+ public function initialize()
+ {
+ $this->authUser = $this->getAuthUser();
+
+ $this->view->setVar('auth_user', $this->authUser);
+ }
+
+ protected function getAuthUser()
+ {
+ /**
+ * @var WebAuth $auth
+ */
+ $auth = $this->getDI()->get('auth');
+
+ return $auth->getCurrentUser();
+ }
+
+}
diff --git a/app/Http/Web/Controllers/MessengerController.php b/app/Http/Web/Controllers/MessengerController.php
index 59a63ddd..f7a69217 100644
--- a/app/Http/Web/Controllers/MessengerController.php
+++ b/app/Http/Web/Controllers/MessengerController.php
@@ -4,11 +4,12 @@ namespace App\Http\Web\Controllers;
use App\Http\Web\Services\Messenger as MessengerService;
use App\Traits\Response as ResponseTrait;
+use Phalcon\Mvc\View;
/**
* @RoutePrefix("/im")
*/
-class MessengerController extends \Phalcon\Mvc\Controller
+class MessengerController extends LayerController
{
use ResponseTrait;
@@ -30,24 +31,11 @@ class MessengerController extends \Phalcon\Mvc\Controller
*/
public function groupMembersAction()
{
- $data = [
- 'list' => [
- [
- 'id' => '1000',
- 'username' => '闲心',
- 'sign' => '我是如此的不寒而栗',
- 'status' => 'online',
- ],
- [
- 'id' => '1001',
- 'username' => '妹儿美',
- 'sign' => '我是如此的不寒而栗',
- 'status' => 'online',
- ]
- ]
- ];
+ $service = new MessengerService();
- return $this->jsonSuccess(['data' => $data]);
+ $list = $service->getGroupUsers();
+
+ return $this->jsonSuccess(['data' => ['list' => $list]]);
}
/**
@@ -63,7 +51,25 @@ class MessengerController extends \Phalcon\Mvc\Controller
*/
public function chatLogAction()
{
+ $service = new MessengerService();
+ $pager = $service->getChatLog();
+
+ $this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
+ $this->view->pick('messenger/chat_log');
+ $this->view->setVar('pager', $pager);
+ }
+
+ /**
+ * @Get("/chat/history", name="im.chat_history")
+ */
+ public function chatHistoryAction()
+ {
+ $service = new MessengerService();
+
+ $pager = $service->getChatLog();
+
+ return $this->jsonPaginate($pager);
}
/**
diff --git a/app/Http/Web/Services/Messenger.php b/app/Http/Web/Services/Messenger.php
index 41210f00..47f9c281 100644
--- a/app/Http/Web/Services/Messenger.php
+++ b/app/Http/Web/Services/Messenger.php
@@ -2,15 +2,20 @@
namespace App\Http\Web\Services;
+use App\Builders\ImMessageList as ImMessageListBuilder;
+use App\Library\Paginator\Query as PagerQuery;
+use App\Models\ImFriendMessage as ImFriendMessageModel;
+use App\Repos\ImChatGroup as ImChatGroupRepo;
+use App\Repos\ImFriendMessage as ImFriendMessageRepo;
+use App\Repos\ImGroupMessage as ImGroupMessageRepo;
use App\Repos\User as UserRepo;
-use App\Services\Frontend\UserTrait;
+use App\Validators\ImChatGroup as ImChatGroupValidator;
+use App\Validators\ImMessage as ImMessageValidator;
use GatewayClient\Gateway;
class Messenger extends Service
{
- use UserTrait;
-
public function init()
{
$user = $this->getLoginUser();
@@ -34,6 +39,77 @@ class Messenger extends Service
];
}
+ public function getGroupUsers()
+ {
+ $id = $this->request->getQuery('id');
+
+ $validator = new ImChatGroupValidator();
+
+ $group = $validator->checkGroupCache($id);
+
+ $groupRepo = new ImChatGroupRepo();
+
+ $users = $groupRepo->findGroupUsers($group->id);
+
+ if ($users->count() == 0) {
+ return [];
+ }
+
+ $baseUrl = kg_ci_base_url();
+
+ $result = [];
+
+ foreach ($users->toArray() as $user) {
+ $user['avatar'] = $baseUrl . $user['avatar'];
+ $result[] = [
+ 'id' => $user['id'],
+ 'username' => $user['name'],
+ 'avatar' => $user['avatar'],
+ 'sign' => $user['sign'],
+ ];
+ }
+
+ return $result;
+ }
+
+ public function getChatLog()
+ {
+ $user = $this->getLoginUser();
+
+ $pagerQuery = new PagerQuery();
+
+ $params = $pagerQuery->getParams();
+
+ $validator = new ImMessageValidator();
+
+ $validator->checkType($params['type']);
+
+ $sort = $pagerQuery->getSort();
+ $page = $pagerQuery->getPage();
+ $limit = $pagerQuery->getLimit();
+
+ if ($params['type'] == 'friend') {
+
+ $params['chat_id'] = ImFriendMessageModel::getChatId($user->id, $params['id']);
+
+ $messageRepo = new ImFriendMessageRepo();
+
+ $pager = $messageRepo->paginate($params, $sort, $page, $limit);
+
+ return $this->handleChatLog($pager);
+
+ } elseif ($params['type'] == 'group') {
+
+ $params['group_id'] = $params['id'];
+
+ $messageRepo = new ImGroupMessageRepo();
+
+ $pager = $messageRepo->paginate($params, $sort, $page, $limit);
+
+ return $this->handleChatLog($pager);
+ }
+ }
+
public function bindUser()
{
$user = $this->getLoginUser();
@@ -54,6 +130,13 @@ class Messenger extends Service
}
}
+ /**
+ * @todo 发送未读消息
+ */
+
+ /**
+ * @todo 发送盒子消息
+ */
}
public function sendMessage()
@@ -74,6 +157,10 @@ class Messenger extends Service
'mine' => false,
];
+ if ($to['type'] == 'group') {
+ $content['id'] = $to['id'];
+ }
+
$message = json_encode([
'type' => 'show_message',
'content' => $content,
@@ -83,12 +170,20 @@ class Messenger extends Service
if ($to['type'] == 'friend') {
- Gateway::sendToUid($to['id'], $message);
+ /**
+ * 不推送自己给自己发送的消息
+ */
+ if ($user->id != $to['id']) {
+ Gateway::sendToUid($to['id'], $message);
+ }
} elseif ($to['type'] == 'group') {
$excludeClientId = null;
+ /**
+ * 不推送自己在群组中发的消息
+ */
if ($user->id == $from['id']) {
$excludeClientId = Gateway::getClientIdByUid($user->id);
}
@@ -176,6 +271,38 @@ class Messenger extends Service
return $result;
}
+ protected function handleChatLog($pager)
+ {
+ if ($pager->total_items == 0) {
+ return $pager;
+ }
+
+ $messages = $pager->items->toArray();
+
+ $builder = new ImMessageListBuilder();
+
+ $users = $builder->getUsers($messages);
+
+ $items = [];
+
+ foreach ($messages as $message) {
+
+ $user = $user = $users[$message['user_id']] ?? new \stdClass();
+
+ $items[] = [
+ 'id' => $message['id'],
+ 'content' => $message['content'],
+ 'create_time' => $message['create_time'],
+ 'timestamp' => $message['create_time'] * 1000,
+ 'user' => $user,
+ ];
+ }
+
+ $pager->items = $items;
+
+ return $pager;
+ }
+
protected function getGroupName($groupId)
{
return "group_{$groupId}";
diff --git a/app/Http/Web/Views/messenger/chat_log.volt b/app/Http/Web/Views/messenger/chat_log.volt
new file mode 100644
index 00000000..151c5bf7
--- /dev/null
+++ b/app/Http/Web/Views/messenger/chat_log.volt
@@ -0,0 +1,91 @@
+
+
+
+
+
+ 聊天记录
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/Http/Web/Views/templates/layer.volt b/app/Http/Web/Views/templates/layer.volt
index 87bcc7a5..95dd51e5 100644
--- a/app/Http/Web/Views/templates/layer.volt
+++ b/app/Http/Web/Views/templates/layer.volt
@@ -3,19 +3,16 @@
-
-
- {{ site_seo.getTitle() }}
{{ icon_link('favicon.ico') }}
{{ css_link('lib/layui/css/layui.css') }}
{{ css_link('web/css/common.css') }}
{% block link_css %}{% endblock %}
{% block inline_css %}{% endblock %}
-
+
{% block content %}{% endblock %}
-{{ js_include('lib/layui/layui.all.js') }}
+{{ js_include('lib/layui/layui.js') }}
{{ js_include('web/js/common.js') }}
{% block include_js %}{% endblock %}
{% block inline_js %}{% endblock %}
diff --git a/app/Repos/ImGroup.php b/app/Repos/ImChatGroup.php
similarity index 86%
rename from app/Repos/ImGroup.php
rename to app/Repos/ImChatGroup.php
index 27a98105..3dd954d3 100644
--- a/app/Repos/ImGroup.php
+++ b/app/Repos/ImChatGroup.php
@@ -3,21 +3,21 @@
namespace App\Repos;
use App\Library\Paginator\Adapter\QueryBuilder as PagerQueryBuilder;
-use App\Models\ImChatGroup as ImGroupModel;
+use App\Models\ImChatGroup as ImChatGroupModel;
use App\Models\ImChatGroupUser as ImGroupUserModel;
use App\Models\User as UserModel;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
-class ImGroup extends Repository
+class ImChatGroup extends Repository
{
public function paginate($where = [], $sort = 'latest', $page = 1, $limit = 15)
{
$builder = $this->modelsManager->createBuilder();
- $builder->from(ImGroupModel::class);
+ $builder->from(ImChatGroupModel::class);
$builder->where('1 = 1');
@@ -52,21 +52,21 @@ class ImGroup extends Repository
/**
* @param int $id
- * @return ImGroupModel|Model|bool
+ * @return ImChatGroupModel|Model|bool
*/
public function findById($id)
{
- return ImGroupModel::findFirst($id);
+ return ImChatGroupModel::findFirst($id);
}
/**
* @param array $ids
* @param string|array $columns
- * @return ResultsetInterface|Resultset|ImGroupModel[]
+ * @return ResultsetInterface|Resultset|ImChatGroupModel[]
*/
public function findByIds($ids, $columns = '*')
{
- return ImGroupModel::query()
+ return ImChatGroupModel::query()
->columns($columns)
->inWhere('id', $ids)
->execute();
diff --git a/app/Repos/ImFriendGroup.php b/app/Repos/ImFriendGroup.php
new file mode 100644
index 00000000..e68ece3e
--- /dev/null
+++ b/app/Repos/ImFriendGroup.php
@@ -0,0 +1,90 @@
+modelsManager->createBuilder();
+
+ $builder->from(ImFriendGroupModel::class);
+
+ $builder->where('1 = 1');
+
+ if (!empty($where['user_id'])) {
+ $builder->andWhere('user_id = :user_id:', ['user_id' => $where['user_id']]);
+ }
+
+ if (!empty($where['name'])) {
+ $builder->andWhere('name LIKE :name:', ['name' => "%{$where['name']}%"]);
+ }
+
+ if (isset($where['deleted'])) {
+ $builder->andWhere('deleted = :deleted:', ['deleted' => $where['deleted']]);
+ }
+
+ switch ($sort) {
+ default:
+ $orderBy = 'id DESC';
+ break;
+ }
+
+ $builder->orderBy($orderBy);
+
+ $pager = new PagerQueryBuilder([
+ 'builder' => $builder,
+ 'page' => $page,
+ 'limit' => $limit,
+ ]);
+
+ return $pager->paginate();
+ }
+
+ /**
+ * @param int $id
+ * @return ImFriendGroupModel|Model|bool
+ */
+ public function findById($id)
+ {
+ return ImFriendGroupModel::findFirst($id);
+ }
+
+ /**
+ * @param array $ids
+ * @param string|array $columns
+ * @return ResultsetInterface|Resultset|ImFriendGroupModel[]
+ */
+ public function findByIds($ids, $columns = '*')
+ {
+ return ImFriendGroupModel::query()
+ ->columns($columns)
+ ->inWhere('id', $ids)
+ ->execute();
+ }
+
+ /**
+ * @param int $groupId
+ * @return ResultsetInterface|Resultset|UserModel[]
+ */
+ public function findGroupUsers($groupId)
+ {
+ return $this->modelsManager->createBuilder()
+ ->columns('u.*')
+ ->addFrom(UserModel::class, 'u')
+ ->join(ImFriendModel::class, 'u.id = f.user_id', 'f')
+ ->where('f.group_id = :group_id:', ['group_id' => $groupId])
+ ->andWhere('u.deleted = 0')
+ ->getQuery()->execute();
+ }
+
+}
diff --git a/app/Repos/ImFriendMessage.php b/app/Repos/ImFriendMessage.php
new file mode 100644
index 00000000..878241b5
--- /dev/null
+++ b/app/Repos/ImFriendMessage.php
@@ -0,0 +1,76 @@
+modelsManager->createBuilder();
+
+ $builder->from(ImFriendMessageModel::class);
+
+ $builder->where('1 = 1');
+
+ if (!empty($where['chat_id'])) {
+ $builder->andWhere('chat_id = :chat_id:', ['chat_id' => $where['chat_id']]);
+ }
+
+ if (!empty($where['user_id'])) {
+ $builder->andWhere('user_id = :user_id:', ['user_id' => $where['user_id']]);
+ }
+
+ if (isset($where['deleted'])) {
+ $builder->andWhere('deleted = :deleted:', ['deleted' => $where['deleted']]);
+ }
+
+ switch ($sort) {
+ case 'oldest':
+ $orderBy = 'id ASC';
+ break;
+ default:
+ $orderBy = 'id DESC';
+ break;
+ }
+
+ $builder->orderBy($orderBy);
+
+ $pager = new PagerQueryBuilder([
+ 'builder' => $builder,
+ 'page' => $page,
+ 'limit' => $limit,
+ ]);
+
+ return $pager->paginate();
+ }
+
+ /**
+ * @param int $id
+ * @return ImFriendMessageModel|Model|bool
+ */
+ public function findById($id)
+ {
+ return ImFriendMessageModel::findFirst($id);
+ }
+
+ /**
+ * @param array $ids
+ * @param string|array $columns
+ * @return ResultsetInterface|Resultset|ImFriendMessageModel[]
+ */
+ public function findByIds($ids, $columns = '*')
+ {
+ return ImFriendMessageModel::query()
+ ->columns($columns)
+ ->inWhere('id', $ids)
+ ->execute();
+ }
+
+}
diff --git a/app/Repos/ImGroupMessage.php b/app/Repos/ImGroupMessage.php
new file mode 100644
index 00000000..c23179fc
--- /dev/null
+++ b/app/Repos/ImGroupMessage.php
@@ -0,0 +1,76 @@
+modelsManager->createBuilder();
+
+ $builder->from(ImGroupMessageModel::class);
+
+ $builder->where('1 = 1');
+
+ if (!empty($where['group_id'])) {
+ $builder->andWhere('group_id = :group_id:', ['group_id' => $where['group_id']]);
+ }
+
+ if (!empty($where['user_id'])) {
+ $builder->andWhere('user_id = :user_id:', ['user_id' => $where['user_id']]);
+ }
+
+ if (isset($where['deleted'])) {
+ $builder->andWhere('deleted = :deleted:', ['deleted' => $where['deleted']]);
+ }
+
+ switch ($sort) {
+ case 'oldest':
+ $orderBy = 'id ASC';
+ break;
+ default:
+ $orderBy = 'id DESC';
+ break;
+ }
+
+ $builder->orderBy($orderBy);
+
+ $pager = new PagerQueryBuilder([
+ 'builder' => $builder,
+ 'page' => $page,
+ 'limit' => $limit,
+ ]);
+
+ return $pager->paginate();
+ }
+
+ /**
+ * @param int $id
+ * @return ImGroupMessageModel|Model|bool
+ */
+ public function findById($id)
+ {
+ return ImGroupMessageModel::findFirst($id);
+ }
+
+ /**
+ * @param array $ids
+ * @param string|array $columns
+ * @return ResultsetInterface|Resultset|ImGroupMessageModel[]
+ */
+ public function findByIds($ids, $columns = '*')
+ {
+ return ImGroupMessageModel::query()
+ ->columns($columns)
+ ->inWhere('id', $ids)
+ ->execute();
+ }
+
+}
diff --git a/app/Validators/ImChatGroup.php b/app/Validators/ImChatGroup.php
new file mode 100644
index 00000000..58e294ab
--- /dev/null
+++ b/app/Validators/ImChatGroup.php
@@ -0,0 +1,88 @@
+get();
+
+ /**
+ * 防止缓存穿透
+ */
+ if ($id < 1 || $id > $maxGroupId) {
+ throw new BadRequestException('im_chat_group.not_found');
+ }
+
+ $groupCache = new ImChatGroupCache();
+
+ $group = $groupCache->get($id);
+
+ if (!$group) {
+ throw new BadRequestException('im_chat_group.not_found');
+ }
+
+ return $group;
+ }
+
+ public function checkGroup($id)
+ {
+ $groupRepo = new ImChatGroupRepo();
+
+ $group = $groupRepo->findById($id);
+
+ if (!$group) {
+ throw new BadRequestException('im_chat_group.not_found');
+ }
+
+ return $group;
+ }
+
+ public function checkName($name)
+ {
+ $value = $this->filter->sanitize($name, ['trim', 'string']);
+
+ $length = kg_strlen($value);
+
+ if ($length < 2) {
+ throw new BadRequestException('im_chat_group.name_too_short');
+ }
+
+ if ($length > 50) {
+ throw new BadRequestException('im_chat_group.name_too_long');
+ }
+
+ return $value;
+ }
+
+ public function checkAbout($name)
+ {
+ $value = $this->filter->sanitize($name, ['trim', 'string']);
+
+ $length = kg_strlen($value);
+
+ if ($length > 255) {
+ throw new BadRequestException('im_chat_group.about_too_long');
+ }
+
+ return $value;
+ }
+
+}
diff --git a/app/Validators/ImFriendGroup.php b/app/Validators/ImFriendGroup.php
new file mode 100644
index 00000000..806b48c0
--- /dev/null
+++ b/app/Validators/ImFriendGroup.php
@@ -0,0 +1,41 @@
+findById($id);
+
+ if (!$group) {
+ throw new BadRequestException('im_friend_group.not_found');
+ }
+
+ return $group;
+ }
+
+ public function checkName($name)
+ {
+ $value = $this->filter->sanitize($name, ['trim', 'string']);
+
+ $length = kg_strlen($value);
+
+ if ($length < 2) {
+ throw new BadRequestException('im_friend_group.name_too_short');
+ }
+
+ if ($length > 15) {
+ throw new BadRequestException('im_friend_group.name_too_long');
+ }
+
+ return $value;
+ }
+
+}
diff --git a/app/Validators/ImMessage.php b/app/Validators/ImMessage.php
new file mode 100644
index 00000000..2e24cdfa
--- /dev/null
+++ b/app/Validators/ImMessage.php
@@ -0,0 +1,64 @@
+findById($id);
+
+ if (!$message) {
+ throw new BadRequestException('im_message.not_found');
+ }
+
+ return $message;
+ }
+
+ public function checkGroupMessage($id)
+ {
+ $messageRepo = new ImGroupMessageRepo();
+
+ $message = $messageRepo->findById($id);
+
+ if (!$message) {
+ throw new BadRequestException('im_message.not_found');
+ }
+
+ return $message;
+ }
+
+ public function checkType($type)
+ {
+ if (!in_array($type, ['friend', 'group'])) {
+ throw new BadRequestException('im_message.invalid_type');
+ }
+
+ return $type;
+ }
+
+ public function checkContent($content)
+ {
+ $value = $this->filter->sanitize($content, ['trim', 'string']);
+
+ $length = kg_strlen($value);
+
+ if ($length < 1) {
+ throw new BadRequestException('im_message.content_too_short');
+ }
+
+ if ($length > 1000) {
+ throw new BadRequestException('im_message.content_too_long');
+ }
+
+ return $value;
+ }
+
+}
diff --git a/public/static/web/css/common.css b/public/static/web/css/common.css
index f2a54b36..cc6bcb3f 100644
--- a/public/static/web/css/common.css
+++ b/public/static/web/css/common.css
@@ -1281,4 +1281,14 @@
.order-item span {
margin-right: 8px;
+}
+
+.layer .layim-chat-main {
+ height: auto;
+}
+
+.layim-chat-user img {
+ width: 40px;
+ height: 40px;
+ border-radius: 100%;
}
\ No newline at end of file