GroupInfo.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. <?php
  2. /**
  3. * 群组相关
  4. * @author solu
  5. */
  6. class GroupInfo extends Model {
  7. protected $tableName = 'group_info';
  8. protected $dbKey = 'dw_chat';
  9. const OFFICIAL_ID = 10000; // 官方群id
  10. const MAX_ADMIN_COUNT = 6; // 最多管理员数量
  11. const REDIS_GROUP_ID_HASH = 'globals:group_id_hash';
  12. const STATE_USE = 1;
  13. const STATE_DISCARD = 0;
  14. /**
  15. * 加入群组
  16. * @author solu
  17. * @param $userId
  18. * @param $groupId
  19. * @param $group
  20. * @return bool
  21. * @throws Exception
  22. */
  23. public function joinGroup($userId, $groupId, $group = []) {
  24. $where = ['group_id' => $groupId];
  25. !$group && $group = $this->objTable->getRow($where);
  26. if (!$group || $group['state'] != self::STATE_USE) {
  27. throw new Exception('group not exists', CODE_PARAM_ERROR);
  28. }
  29. $objSession = new Session();
  30. $objUserGroup = new TableHelper('user_group', 'dw_chat');
  31. $row = $objUserGroup->getRow(['user_id' => $userId, 'group_id' => $groupId, 'state' => UserGroup::STATE_IN_GROUP]);
  32. if (!$row) {
  33. $data = [
  34. 'user_id' => $userId,
  35. 'group_id' => $groupId,
  36. 'join_time' => NOW,
  37. 'state' => UserGroup::STATE_IN_GROUP,
  38. ];
  39. if (!$objUserGroup->replaceObject($data)) {
  40. throw new Exception('join fail', CODE_DB_ERROR);
  41. }
  42. $memberNum = $group['member_num'] + 1;
  43. $where['member_num'] = $group['member_num'];
  44. if (!$this->objTable->updateObject(['member_num' => $memberNum], $where)) {
  45. throw new Exception('update group info fail', CODE_DB_ERROR);
  46. }
  47. // 给自己发消息订阅新群组
  48. ThirdApi::pushPersonEvent($userId, [
  49. 'type' => 'join_group',
  50. 'group_id' => $groupId,
  51. ]);
  52. // 发群消息
  53. try {
  54. $objSession->sendGroupMsg($userId, $groupId, Session::MSG_TYPE_EVENT, '', false, true, ['event_type' => 'join']);
  55. } catch (Exception $e) {
  56. var_log($e->getMessage());
  57. }
  58. // $user_info = User::getUserInfoById($userId);
  59. // // 给群组发消息有人加入了
  60. // ThirdApi::pushGroupEvent($groupId, [
  61. // 'type' => 'join',
  62. // 'group_id' => $groupId,
  63. // 'user_info' => $user_info,
  64. // ]);
  65. }
  66. $where = [
  67. 'user_id' => $userId,
  68. 'session_id' => $groupId,
  69. ];
  70. // 有可能删除了会话
  71. if (!$objSession->objTable->getCount($where)) {
  72. $sessData = $where;
  73. $sessData['is_group'] = 1;
  74. $objSession->objTable->replaceObject($sessData);
  75. }
  76. return true;
  77. }
  78. /**
  79. * 加入群组
  80. * @author solu
  81. * @param $userId
  82. * @param $groupId
  83. * @param $group
  84. * @param $memberNum
  85. * @return bool
  86. * @throws Exception
  87. */
  88. public function appendToGroup($userIds, $groupId, $memberNum = 0) {
  89. $userGroupDatas = [];
  90. $sessDatas = [];
  91. foreach ($userIds as $userId) {
  92. $userGroupDatas[] = [
  93. 'user_id' => $userId,
  94. 'group_id' => $groupId,
  95. 'join_time' => NOW,
  96. 'state' => UserGroup::STATE_IN_GROUP,
  97. ];
  98. $sessDatas[] = [
  99. 'user_id' => $userId,
  100. 'session_id' => $groupId,
  101. 'is_group' => 1,
  102. ];
  103. }
  104. $objUserGroup = new UserGroup();
  105. $objUserGroup->objTable->replaceObjects2($userGroupDatas);
  106. $objSession = new Session();
  107. $objSession->objTable->replaceObjects2($sessDatas);
  108. $where = ['group_id' => $groupId];
  109. $newData = [
  110. 'member_num' => count($userIds) + $memberNum,
  111. 'group_name' => $groupId,
  112. ];
  113. $this->objTable->updateObject($newData, $where);
  114. }
  115. /**
  116. * 离开群组
  117. * @author solu
  118. * @param $userId
  119. * @param $groupId
  120. * @param $group
  121. * @return bool
  122. * @throws Exception
  123. */
  124. public function leaveGroup($userId, $groupId, $group = []) {
  125. $groupId = (int) $groupId;
  126. $where = ['group_id' => $groupId];
  127. !$group && $group = $this->objTable->getRow($where);
  128. if (!$group) {
  129. throw new Exception('group not exists', CODE_PARAM_ERROR);
  130. }
  131. $objUserGroup = new UserGroup();
  132. if (!$objUserGroup->inGroup($groupId, $userId)) {
  133. throw new Exception('not in group', CODE_PARAM_ERROR);
  134. }
  135. $groupUserIds = $objUserGroup->getUserIdListSortByAdminAndJoinTime($groupId);
  136. $isCreator = $this->isCreator($groupId, $userId);
  137. $objSession = new Session();
  138. $objUserGroup->objTable->autoCommit(false);
  139. $ugWhere = [
  140. 'user_id' => $userId,
  141. 'group_id' => $groupId,
  142. ];
  143. if (!$objUserGroup->objTable->updateObject(['state' => UserGroup::STATE_LEAVE, 'is_admin' => 0], $ugWhere)) {
  144. throw new Exception('leave fail', CODE_DB_ERROR);
  145. }
  146. $creatorField = '';
  147. if (count($groupUserIds) == 1) { // 最后一个人离开,去掉群主,群解散
  148. $creatorField = ' , creator=0';
  149. } elseif ($isCreator) { // 群主离开, 群主顺移给下一位
  150. $newCreator = 0;
  151. for ($i = 0; $i < count($groupUserIds); $i++) {
  152. if ($groupUserIds[$i] == $userId) {
  153. continue;
  154. }
  155. $newCreator = $groupUserIds[$i];
  156. break;
  157. }
  158. $creatorField = " , creator={$newCreator}";
  159. // 新群主设置成管理员
  160. $objUserGroup->setData($groupId, $newCreator, ['is_admin' => 1]);
  161. }
  162. $sql = "UPDATE group_info SET member_num = member_num - 1 {$creatorField} WHERE group_id = {$groupId}";
  163. $this->objDb->update($sql);
  164. $sessWhere = [
  165. 'user_id' => $userId,
  166. 'session_id' => $groupId,
  167. ];
  168. if (!$objSession->objTable->delObject($sessWhere)) {
  169. throw new Exception('delete session error', CODE_DB_ERROR);
  170. }
  171. $objUserGroup->objTable->commit();
  172. // 发群消息
  173. try {
  174. $objSession->sendGroupMsg($userId, $groupId, Session::MSG_TYPE_EVENT, '', false, true, ['event_type' => 'leave_group']);
  175. } catch (Exception $e) {
  176. var_log($e->getMessage());
  177. }
  178. // 给自己发消息订阅新群组
  179. ThirdApi::pushPersonEvent($userId, [
  180. 'type' => 'leave_group',
  181. 'group_id' => $groupId,
  182. ]);
  183. // $user_info = User::getUserInfoById($userId);
  184. //
  185. // // 给群组发消息有人离开了
  186. // ThirdApi::pushGroupEvent($groupId, [
  187. // 'type' => 'leave',
  188. // 'group_id' => $groupId,
  189. // 'user_id' => $userId,
  190. // 'user_info' => $user_info,
  191. // ]);
  192. return true;
  193. }
  194. /**
  195. * 更新信息
  196. * @author solu
  197. * @param $userId
  198. * @param $groupId
  199. * @param array $data
  200. * @return bool|int
  201. * @throws Exception
  202. */
  203. public function setData($userId, $groupId, array $data) {
  204. if (!(new UserGroup())->isAdmin($groupId, $userId)) {
  205. throw new Exception('no permission', CODE_NO_PERMITION);
  206. }
  207. $data['update_time'] = NOW;
  208. return $this->objTable->updateObject($data, ['group_id' => $groupId]);
  209. }
  210. /**
  211. * 分享连接
  212. * @author solu
  213. * @param string $name
  214. * @return string
  215. */
  216. public static function genInviteUrl($name) {
  217. if (!$name) {
  218. return '';
  219. }
  220. return sprintf('%s/s/%s', URL_SELF, $name);
  221. }
  222. /**
  223. * 是否群主
  224. * @author solu
  225. * @param $groupId
  226. * @param $userId
  227. * @return bool
  228. */
  229. public function isCreator($groupId, $userId) {
  230. $creator = $this->objTable->getOne(['group_id' => $groupId], ['_field' => 'creator']);
  231. return intval($creator) == $userId;
  232. }
  233. /**
  234. * 设置群管理
  235. * @author solu
  236. * @param $groupId
  237. * @param $creator
  238. * @param $userIds
  239. * @return bool
  240. * @throws Exception
  241. */
  242. public function addAdmin($groupId, $creator, $userIds) {
  243. if (!$this->isCreator($groupId, $creator)) {
  244. throw new Exception('no permission', CODE_NO_PERMITION);
  245. }
  246. $objUserGroup = new UserGroup();
  247. $c = $objUserGroup->objTable->getCount(['group_id' => $groupId, 'is_admin' => 1]);
  248. $c += count($userIds);
  249. if ($c > self::MAX_ADMIN_COUNT) {
  250. throw new Exception('too many admins', CODE_NORMAL_ERROR);
  251. }
  252. if (!$objUserGroup->setData($groupId, $userIds, ['is_admin' => 1])) {
  253. throw new Exception('set admin fail', CODE_NORMAL_ERROR);
  254. }
  255. return true;
  256. }
  257. /**
  258. * 移除管理员
  259. * @author solu
  260. * @param $groupId
  261. * @param $creator
  262. * @param $userId
  263. * @return bool
  264. * @throws Exception
  265. */
  266. public function removeAdmin($groupId, $creator, $userId) {
  267. if (!$this->isCreator($groupId, $creator)) {
  268. throw new Exception('no permission', CODE_NO_PERMITION);
  269. }
  270. if ($userId == $creator) {
  271. throw new Exception('can not remove creator', CODE_NORMAL_ERROR);
  272. }
  273. $objUserGroup = new UserGroup();
  274. if (!$objUserGroup->setData($groupId, $userId, ['is_admin' => 0])) {
  275. throw new Exception('set admin fail', CODE_NORMAL_ERROR);
  276. }
  277. return true;
  278. }
  279. /**
  280. * 搜索群用户信息
  281. * @author solu
  282. * @param $groupId
  283. * @param $keyword
  284. * @return array
  285. */
  286. public function memberSearch($groupId, $keyword) {
  287. $objUserGroup = new UserGroup();
  288. $userIds = $objUserGroup->objTable->getCol(['group_id' => $groupId], ['_field' => 'user_id']);
  289. if (!$userIds) {
  290. return [];
  291. }
  292. $objUserInfo = new TableHelper('user_info', 'dw_chat');
  293. // $keyword 是外部传来的参数,会有注入攻击
  294. $keyword = $objUserInfo->escape($keyword);
  295. $userInfo = $objUserInfo->getAll(['user_id' => $userIds], [
  296. '_where' => "(user_name like '{$keyword}%' || nick_name like '{$keyword}%')",
  297. '_field' => 'user_id, user_name, nick_name, cover_photo',
  298. '_sortKey' => 'last_login_time DESC',
  299. // '_debug' => true,
  300. ]);
  301. coverReplaceArrImage($userInfo, 'cover_photo');
  302. return $userInfo;
  303. }
  304. /**
  305. * 获取用户名
  306. * @author solu
  307. * @param $groupId
  308. * @return string
  309. */
  310. public static function getGroupNameById($groupId) {
  311. $objRedis = dwRedis::init();
  312. $userName = $objRedis->hGet(self::REDIS_GROUP_ID_HASH, $groupId);
  313. if (!$userName) {
  314. $obj = new GroupInfo();
  315. $userName = $obj->objTable->getOne(['group_id' => $groupId], ['_field' => 'group_title']);
  316. if ($userName) {
  317. self::setGroupNameById($groupId, $userName, $objRedis);
  318. }
  319. }
  320. return $userName;
  321. }
  322. public static function setGroupNameById($groupId, $user_name, $objRedis = null) {
  323. !$objRedis && $objRedis = dwRedis::init();
  324. $objRedis->hSet(self::REDIS_GROUP_ID_HASH, $groupId, $user_name);
  325. }
  326. /**
  327. * 转移群主
  328. * @author solu
  329. * @param $groupId
  330. * @param $old
  331. * @param $new
  332. * @return bool
  333. * @throws Exception
  334. */
  335. public function changeCreator($groupId, $old, $new) {
  336. if (!$this->isCreator($groupId, $old)) {
  337. throw new Exception('no permission', CODE_NO_PERMITION);
  338. }
  339. if ($old == $new) {
  340. throw new Exception('same creator', CODE_NORMAL_ERROR);
  341. }
  342. $this->objTable->updateObject(['creator' => $new], ['group_id' => $groupId]);
  343. (new UserGroup())->setData($groupId, $old, ['is_admin' => 0]);
  344. return true;
  345. }
  346. /**
  347. * 获取客服id
  348. * @author solu
  349. * @param $userIds
  350. * @return array
  351. */
  352. public function getGroupServerFromUserIds($userIds) {
  353. $items = $this->objTable->getAll(['server_id' => $userIds], ['_field' => 'group_id, server_id']);
  354. return arrayFormatKey($items, 'server_id', 'group_id');
  355. }
  356. public function checkPermission($group_id, $user_id) {
  357. $_field = 'is_public';
  358. $objGroupInfo = new GroupInfo();
  359. $is_public = (int) $objGroupInfo->objTable->getOne(['group_id' => $group_id], compact('_field'));
  360. if (!$is_public) {
  361. // 私有群要判断用户是否存在
  362. if ($user_id) {
  363. $objUserGroup = new UserGroup();
  364. $state = 1;
  365. $count = $objUserGroup->objTable->getCount(compact('group_id', 'user_id', 'state'));
  366. if (!$count) {
  367. Response::error(CODE_NO_PERMITION, 'the group is private');
  368. }
  369. } else {
  370. Response::error(CODE_NO_PERMITION, 'the group is private');
  371. }
  372. }
  373. }
  374. public function getGroupIdFromTgGroupId($tgGroupId) {
  375. $groupId = $this->objTable->getOne(['tg_group_id' => $tgGroupId], ['_field' => 'group_id']);
  376. return intval($groupId);
  377. }
  378. /**
  379. * 热门群组
  380. * @return array
  381. */
  382. public function getHotList() {
  383. $list = $this->objTable->getAll(['is_auth' => 1, 'is_public' => 1], ['_limit' => 100]);
  384. $fillNum = 24 - count($list);
  385. if ($fillNum > 0) {
  386. // 找用户自发来补充
  387. $keyWord = ['_limit' => 200, '_sortKey' => 'member_num DESC'];
  388. $list2 = $this->objTable->getAll(['is_auth' => 0, 'is_public' => 1], $keyWord);
  389. // 随机取10个
  390. $list2 = $this->array_random_assoc($list2, 24 - count($list));
  391. $list = array_merge($list, $list2);
  392. }
  393. $group_ids = array_column($list, 'group_id');
  394. $existList = [];
  395. $userId = User::getUserId();
  396. if ($userId) {
  397. $_field = 'group_id';
  398. $objUserGroup = new UserGroup();
  399. $where = ['group_id' => $group_ids, 'state' => 1, 'user_id' => $userId];
  400. $existList = $objUserGroup->objTable->getCol($where, ['_field' => $_field]);
  401. }
  402. foreach ($list as $i => $value) {
  403. $value['is_join'] = in_array($value['group_id'], $existList) ? 1 : 0;
  404. $value['cover_photo'] = awsReplaceImg($value['cover_photo']);
  405. $list[$i] = $value;
  406. }
  407. return $list;
  408. }
  409. private function array_random_assoc($arr, $num = 1) {
  410. $keys = array_keys($arr);
  411. shuffle($keys);
  412. $r = array();
  413. for ($i = 0; $i < $num; $i++) {
  414. $r[$keys[$i]] = $arr[$keys[$i]];
  415. }
  416. return $r;
  417. }
  418. /**
  419. * 创建群
  420. * @param $args
  421. * @return array|null
  422. * @throws Exception
  423. */
  424. public function create($args) {
  425. $creator = $args['creator'];
  426. $user_id_list = arrayPop($args, 'user_id_list');
  427. if (!$creator) {
  428. throw new Exception("miss creator", CODE_PARAM_ERROR);
  429. }
  430. $state = self::STATE_USE;
  431. $num = $this->objTable->getCount(compact('creator', 'state'));
  432. if ($num >= 100) {
  433. throw new Exception("create group num >= {$num}", CODE_NO_PERMITION);
  434. }
  435. $args['server_id'] = $args['creator'];
  436. $memberNum = 0;
  437. $args['member_num'] = $memberNum;
  438. $args['create_time'] = $args['update_time'] = NOW;
  439. $this->objTable->addObject($args);
  440. $group_id = $this->objTable->getInsertId();
  441. // 设置group_name
  442. $this->objTable->updateObject(['group_name' => $group_id], ['group_id' => $group_id]);
  443. // 默认加群
  444. $this->joinGroup($creator, $group_id);
  445. $memberNum++;
  446. // 设置为管理员
  447. $objUserGroup = new UserGroup();
  448. $data = [
  449. 'is_admin' => 1,
  450. 'state' => UserGroup::STATE_IN_GROUP,
  451. ];
  452. $objUserGroup->setData($group_id, $creator, $data);
  453. $friend_ids = $this->_getFriendList($creator, $user_id_list);
  454. if ($friend_ids) {
  455. $this->appendToGroup($friend_ids, $group_id, $memberNum);
  456. }
  457. $objSession = new Session();
  458. $_field = 'session_id, is_group, read_hash, is_pin, pin_time_int, is_mute';
  459. $sesion = $objSession->objTable->getRow(['session_id' => $group_id], compact('_field'));
  460. $sesion['name'] = $args['group_title'];
  461. $sesion['cover_photo'] = $args['cover_photo'];
  462. return $sesion;
  463. }
  464. // 只过滤出真正的朋友
  465. private function _getFriendList($userId, $friend_id_list) {
  466. $friend_ids = explode(',', $friend_id_list);
  467. $map = [];
  468. foreach ($friend_ids as $friend_id) {
  469. $friend_id = (int) $friend_id;
  470. $sessionId = Session::getPersonSessionId($userId, $friend_id);
  471. $map[$sessionId] = $friend_id;
  472. }
  473. if (!$map) {
  474. return [];
  475. }
  476. $where = [
  477. 'user_id' => $userId,
  478. 'session_id' => array_keys($map),
  479. ];
  480. $_field = 'session_id';
  481. $objSession = new Session();
  482. $sessionIds = $objSession->objTable->getCol($where, compact('_field'));
  483. if (!$sessionIds) return [];
  484. $friend_ids = [];
  485. foreach ($sessionIds as $sessionId) {
  486. $friend_ids[] = $map[$sessionId];
  487. }
  488. return $friend_ids;
  489. }
  490. /**
  491. * 删除群
  492. * @author solu
  493. * @param $creator
  494. * @param $groupId
  495. * @return bool
  496. * @throws Exception
  497. */
  498. public function discard($creator, $groupId) {
  499. if (!$this->objTable->getRow(['group_id' => $groupId, 'creator' => $creator])) {
  500. throw new Exception('group not found', CODE_PARAM_ERROR);
  501. }
  502. $objUserGroup = new UserGroup();
  503. $objSession = new Session();
  504. $this->objTable->autoCommit(false);
  505. $this->objTable->updateObject(['member_num' => 0, 'state' => self::STATE_DISCARD], ['group_id' => $groupId]);
  506. $objUserGroup->objTable->updateObject(['state' => UserGroup::STATE_LEAVE], ['group_id' => $groupId]);
  507. $objSession->objTable->delObject(['session_id' => $groupId, 'is_group' => 1]);
  508. $this->objTable->commit();
  509. return true;
  510. }
  511. }