Session.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. <?php
  2. /**
  3. * 会话信息
  4. * @author benzhan
  5. */
  6. class Session extends Model {
  7. protected $tableName = 'session';
  8. protected $dbKey = 'dw_chat';
  9. const MSG_TYPE_REPEAL = -1; //被撤销
  10. const MSG_TYPE_TEXT = 0;
  11. const MSG_TYPE_IMAGE = 1;
  12. const MSG_TYPE_VIDEO = 2;
  13. const MSG_TYPE_AUDIO = 3;
  14. const MSG_TYPE_REDPACK = 4;
  15. const MSG_TYPE_EVENT = 5;
  16. const LAST_MSG_NUM_HASH = 'globals:last_msg_num_hash';
  17. const LAST_MSG_HASH = 'globals:last_msg_hash';
  18. /**
  19. * 填充会话列表
  20. * @param string $user_id 用户id
  21. * @param array $list 会话列表
  22. * @param int $currentGroupId 当前群id
  23. *
  24. * @return mixed
  25. */
  26. public function fillSession($user_id, $list, $currentGroupId) {
  27. $group_ids = [];
  28. $user_ids = [];
  29. foreach ($list as $item) {
  30. if ($item['is_group']) {
  31. $group_ids[] = $item['session_id'];
  32. } else {
  33. // 把自己的用户名换掉
  34. $_tmp = explode('-', $item['session_id']);
  35. foreach ($_tmp as $_uid) {
  36. $user_ids[$_uid] = 1;
  37. }
  38. }
  39. }
  40. unset($user_ids[$user_id]);
  41. $user_ids = array_keys($user_ids);
  42. $objUserInfo = new TableHelper('user_info', 'dw_chat');
  43. $_field = 'user_id, user_name, nick_name, cover_photo';
  44. $users = $objUserInfo->getAll(['user_id' => $user_ids], compact('_field'));
  45. $users = arrayFormatKey($users, 'user_id');
  46. $objGroupInfo = new TableHelper('group_info', 'dw_chat');
  47. $_field = 'group_id, group_name, group_title, cover_photo, is_auth';
  48. $groups = $objGroupInfo->getAll(['group_id' => $group_ids], compact('_field'));
  49. $groups = arrayFormatKey($groups, 'group_id');
  50. $userGroups = [];
  51. if ($currentGroupId) {
  52. $objUserGroup = new UserGroup();
  53. $userGroups = $objUserGroup->objTable->getAll(['user_id' => $user_ids, 'group_id' => $currentGroupId], ['_field' => 'user_id, group_id']);
  54. $userGroups = arrayFormatKey($userGroups, 'user_id', 'group_id');
  55. }
  56. $objRedis = dwRedis::init(Eos::REDIS_SERV);
  57. $fieldTime = [];
  58. $fieldPin = [];
  59. foreach ($list as $i => $item) {
  60. if ($item['is_group']) {
  61. $group = $groups[$item['session_id']];
  62. $item['name'] = $group['group_title'] ?: $group['group_name'];
  63. $item['cover_photo'] = $group['cover_photo'];
  64. $item['is_auth'] = $group['is_auth'];
  65. } else {
  66. // 把自己的用户名换掉
  67. $key = self::getToUser($user_id, $item['session_id']);
  68. $user = $users[$key];
  69. $item['name'] = $user['nick_name'];
  70. $item['cover_photo'] = $user['cover_photo'];
  71. $item['in_group'] = $userGroups[$key] ? 1 : 0; // 已经在群
  72. }
  73. $lastNum = self::getLastMsgNum($item['session_id']);
  74. $item['unread'] = max($lastNum - $item['read_num'], 0);
  75. $item['cover_photo'] = coverReplaceImage($item['cover_photo']);
  76. $item['last_msg'] = self::getLastMsg($item['session_id'], $objRedis);
  77. $item['update_time_int'] = $item['last_msg']['time'] ?: 0;
  78. $fieldPin[] = $item['pin_time_int'];
  79. $fieldTime[] = $item['update_time_int'];
  80. $list[$i] = $item;
  81. }
  82. array_multisort($fieldPin, SORT_DESC, $fieldTime, SORT_DESC, $list);
  83. return $list;
  84. }
  85. public function getMsgList($session_id, $read_hash, $load_type, $is_group) {
  86. $where2 = [];
  87. if ($is_group) {
  88. $_field = '`hash`, `from`, `msg`, `msg_type`, `state`, create_time_int, msg_num, ext_info';
  89. $objMsg = new TableHelper('group_msg', 'dw_chat');
  90. $where2['group_id'] = $session_id;
  91. } else {
  92. $_field = '`hash`, `from`, `to`, `msg`, `msg_type`, `state`, create_time_int, msg_num, ext_info';
  93. $objMsg = new TableHelper('person_msg', 'dw_chat');
  94. $where2['session_id'] = $session_id;
  95. }
  96. if ($read_hash) {
  97. $currentRow = $objMsg->getRow(['hash' => $read_hash], compact('_field'));
  98. $create_time_int = (int) $currentRow['create_time_int'];
  99. } else {
  100. $create_time_int = 0;
  101. }
  102. $_limit = 50;
  103. if ($load_type == 0) {
  104. $create_time_int && $_where = "create_time_int >= {$create_time_int}";
  105. $_sortKey = "create_time_int DESC";
  106. $keyWord2 = compact('_where', '_sortKey', '_limit', '_field');
  107. $list = $objMsg->getAll($where2, $keyWord2);
  108. } else {
  109. // 加载历史记录
  110. $_where = "create_time_int < {$create_time_int}";
  111. $_sortKey = "create_time_int DESC";
  112. $keyWord3 = compact('_where', '_sortKey', '_limit', '_field');
  113. $list = $objMsg->getAll($where2, $keyWord3);
  114. }
  115. $list = array_reverse($list);
  116. $list = $this->appendExtInfo($list);
  117. $user_ids = array_column($list, 'from');
  118. if (!$is_group) {
  119. $to_ids = array_column($list, 'to');
  120. $user_ids = array_merge($user_ids, $to_ids);
  121. }
  122. $user_ids = array_unique($user_ids);
  123. $userMap = $this->getUserMap($user_ids);
  124. return compact('userMap', 'list');
  125. }
  126. /**
  127. * 额外信息
  128. * @author solu
  129. * @param $list
  130. * @return mixed
  131. */
  132. private function appendExtInfo($list) {
  133. $userId = User::getUserId();
  134. $objRedpackLog = new RedpackLog();
  135. $trxIds = array_map(function($v) {
  136. $msg = Utils::decodeRC4($v['msg']);
  137. $data = json_decode($msg, true);
  138. return $data['trxId'];
  139. }, array_filter($list, function($v) {
  140. return $v['msg_type'] == self::MSG_TYPE_REDPACK;
  141. }));
  142. $redpack = [];
  143. if ($trxIds) {
  144. $objRedpack = new Redpack();
  145. $redpack = $objRedpack->objTable->getAll(['transfer_trx_id' => $trxIds], ['_field' => 'transfer_trx_id, status']);
  146. $redpack = arrayFormatKey($redpack, 'transfer_trx_id', 'status');
  147. }
  148. foreach ($list as $k => $v) {
  149. if ($v['msg_type'] == self::MSG_TYPE_REDPACK) {
  150. $msg = Utils::decodeRC4($v['msg']);
  151. $data = json_decode($msg, true);
  152. $trxId = $data['trxId'];
  153. $v['ext']['grabbed'] = $objRedpackLog->userGrabbed($userId, $trxId);
  154. $v['ext']['redpack_status'] = intval($redpack[$trxId]);
  155. }
  156. $v['ext_info'] && $v['ext_info'] = json_decode($v['ext_info'], true);
  157. $list[$k] = $v;
  158. }
  159. return $list;
  160. }
  161. public function getUserMap($user_ids) {
  162. $objUserInfo = new TableHelper('user_info', 'dw_chat');
  163. $datas = $objUserInfo->getAll(['user_id' => $user_ids], ['_field' => 'user_id, user_name, nick_name, cover_photo']);
  164. coverReplaceArrImage($datas, 'cover_photo');
  165. return arrayFormatKey($datas, 'user_id');
  166. }
  167. /**
  168. * 修改状态
  169. * @param $session_id
  170. * @param $newData
  171. * @param $ext;
  172. */
  173. public function updateState($session_id, $newData, $ext = []) {
  174. $where = compact('session_id');
  175. $where = array_merge($where, $ext);
  176. $newData['update_time'] = NOW;
  177. $newData['update_time_int'] = microtime(true) * 1000;
  178. $this->objTable->updateObject($newData, $where);
  179. }
  180. /**
  181. * 检测私聊session
  182. * @author solu
  183. * @param $from
  184. * @param $sessionId
  185. * @return array
  186. * @throws Exception
  187. */
  188. public function checkPersonSession($from, $sessionId) {
  189. $uids = explode('-', $sessionId);
  190. $uids = array_filter($uids, function ($v) {return $v > 0;});
  191. // if (count($uids) != 2 || !$this->objTable->getRow(['user_id' => $from, 'session_id' => $sessionId])) {
  192. // throw new Exception('session_id error', CODE_PARAM_ERROR);
  193. // }
  194. if (!in_array($from, $uids)) {
  195. throw new Exception('user not in session', CODE_NO_PERMITION);
  196. }
  197. $to = 0;
  198. foreach ($uids as $uid) {
  199. if ($uid != $from) {
  200. $to = $uid;
  201. break;
  202. }
  203. }
  204. return [$from, $to];
  205. }
  206. /**
  207. * 检测群session
  208. * @author solu
  209. * @param $from
  210. * @param $sessionId
  211. * @return bool
  212. * @throws Exception
  213. */
  214. public function checkGroupSession($from, $sessionId) {
  215. if (!$this->objTable->getRow(['user_id' => $from, 'session_id' => $sessionId])) {
  216. throw new Exception('user not in session', CODE_NO_PERMITION);
  217. }
  218. return true;
  219. }
  220. public static function getPersonSessionId($from, $to) {
  221. if ($from > $to) {
  222. return "{$to}-{$from}";
  223. } else {
  224. return "{$from}-{$to}";
  225. }
  226. }
  227. private function initPersonSession($from, $to) {
  228. $session_id = self::getPersonSessionId($from, $to);
  229. $num = $this->objTable->getCount(compact('session_id'));
  230. if ($num < 2) { // 单方面删除会话的情况
  231. // 插入双方的session
  232. $datas = [[
  233. 'user_id' => $from,
  234. 'session_id' => $session_id,
  235. 'is_group' => 0,
  236. ], [
  237. 'user_id' => $to,
  238. 'session_id' => $session_id,
  239. 'is_group' => 0,
  240. ]
  241. ];
  242. // 第一次初始化,需要初始化
  243. $this->objTable->addObjectsIfNoExist($datas);
  244. }
  245. }
  246. /**
  247. * 发送私聊消息
  248. * @author solu
  249. * @param $from
  250. * @param $sessionId
  251. * @param $msg_type
  252. * @param $msg
  253. * @param $noEvent
  254. * @param $extInfo
  255. * @return array
  256. * @throws Exception
  257. */
  258. public function sendPersonMsg($from, $sessionId, $msg_type, $msg, $noEvent = false, $extInfo = []) {
  259. list($from, $to) = $this->checkPersonSession($from, $sessionId);
  260. $this->initPersonSession($from, $to);
  261. $t = self::getMS();
  262. 0 == $msg_type && $msg = htmlentities($msg);
  263. $lastNum = self::incrLastMsgNum($sessionId);
  264. $data = [
  265. 'session_id' => $sessionId,
  266. 'from' => intval($from),
  267. 'to' => intval($to),
  268. 'msg_type' => $msg_type,
  269. 'msg' => $msg,
  270. 'create_time' => NOW,
  271. 'create_time_int' => $t,
  272. 'msg_num' => $lastNum,
  273. ];
  274. $extInfo && $data['ext_info'] = json_encode($extInfo);
  275. $data['hash'] = self::_genHash($data);
  276. $objPersonMsg = new TableHelper('person_msg', 'dw_chat');
  277. if (!$objPersonMsg->addObject($data)) {
  278. throw new Exception('send message error', CODE_NORMAL_ERROR);
  279. }
  280. // 更新发送人已读序号
  281. $this->updateState($sessionId, ['read_num' => $lastNum], ['user_id' => $from]);
  282. $name = User::getUserNameById($from);
  283. $content = self::_msgHandle($msg, $msg_type);
  284. $eventData = [
  285. 'type' => 'msg',
  286. 'msg_type' => $msg_type,
  287. 'from' => intval($from),
  288. 'to' => intval($to),
  289. 'name' => $name,
  290. 'nick_name' => User::getUserNameById($from),
  291. 'content' => $content,
  292. 'hash' => $data['hash'],
  293. 'timestamp' => $t,
  294. 'ext_info' => $extInfo,
  295. ];
  296. !$noEvent && ThirdApi::pushPersonEvent($to, $eventData);
  297. !$noEvent && ThirdApi::pushPersonEvent($from, $eventData);
  298. self::setLastMsg($sessionId, $msg_type, $content, $from, $name, $extInfo);
  299. $eventData['content'] = $msg;
  300. return $eventData;
  301. }
  302. /**
  303. * 发送群聊消息
  304. * @author solu
  305. * @param $from
  306. * @param $groupId
  307. * @param $msg_type
  308. * @param $msg
  309. * @param $noEvent
  310. * @param $noTg
  311. * @param $extInfo
  312. * @return array
  313. * @throws Exception
  314. */
  315. public function sendGroupMsg($from, $groupId, $msg_type, $msg, $noEvent = false, $noTg = false, $extInfo = []) {
  316. $userMap = null;
  317. $objGroupInfo = new GroupInfo();
  318. if ($extInfo['event_type'] != 'leave_group' && !$this->objTable->getRow(['user_id' => $from, 'session_id' => $groupId])) {
  319. // 聊天就自动加入群
  320. // 检查私有群的权限
  321. $objGroupInfo->checkPermission($groupId, $from);
  322. // 第一次发言,需要返回用户信息
  323. $objGroupInfo->joinGroup($from, $groupId);
  324. $userMap = $this->getUserMap($from);
  325. } else if ((new UserGroup())->isBlock($groupId, $from)) {
  326. throw new Exception('Banned', CODE_NORMAL_ERROR);
  327. }
  328. $t = self::getMS();
  329. 0 == $msg_type && $msg = htmlentities($msg);
  330. $lastNum = self::incrLastMsgNum($groupId);
  331. $data = [
  332. 'group_id' => intval($groupId),
  333. 'from' => $from,
  334. 'msg_type' => $msg_type,
  335. 'msg' => $msg,
  336. 'create_time' => NOW,
  337. 'create_time_int' => $t,
  338. 'msg_num' => $lastNum,
  339. ];
  340. $extInfo && $data['ext_info'] = json_encode($extInfo);
  341. $data['hash'] = self::_genHash($data);
  342. $objGroupMsg = new TableHelper('group_msg', 'dw_chat');
  343. if (!$objGroupMsg->addObject($data)) {
  344. throw new Exception('send message error', CODE_NORMAL_ERROR);
  345. }
  346. // 更新发送人已读序号
  347. $this->updateState($groupId, ['read_num' => $lastNum], ['user_id' => $from]);
  348. $content = self::_msgHandle($msg, $msg_type);
  349. $name = GroupInfo::getGroupNameById($groupId);
  350. $eventData = [
  351. 'type' => 'msg',
  352. 'msg_type' => $msg_type,
  353. 'from' => $from,
  354. 'name' => $name,
  355. 'nick_name' => User::getUserNameById($from),
  356. 'content' => $content,
  357. 'hash' => $data['hash'],
  358. 'timestamp' => $t,
  359. 'ext_info' => $extInfo,
  360. ];
  361. !$noEvent && ThirdApi::pushGroupEvent($groupId, $eventData);
  362. self::setLastMsg($groupId, $msg_type, $content, $from, $name, $extInfo);
  363. $eventData['content'] = $msg;
  364. $eventData['userMap'] = $userMap;
  365. // 推送到Telegram
  366. $group = $objGroupInfo->objTable->getRow(['group_id' => $groupId], ['_field' => 'group_name, tg_group_id']);
  367. if ($group['tg_group_id'] && !$noTg && $msg_type != self::MSG_TYPE_REPEAL) {
  368. // 特殊处理红包
  369. if ($msg_type == self::MSG_TYPE_REDPACK) {
  370. $text = "
  371. <a href='https://{$_SERVER['HTTP_HOST']}/s/{$group['group_name']}'>[收到MeeChat红包,请打开MeeChat查看]</a>";
  372. } else {
  373. $text = Utils::decodeRC4($msg);
  374. }
  375. $text = "<b>{$eventData['nick_name']}</b>:
  376. {$text}";
  377. Telegram::pushMessageList(['chat_id' => $group['tg_group_id'], "text" => $text, 'parse_mode' => 'HTML']);
  378. }
  379. return $eventData;
  380. }
  381. private static function _genHash($data) {
  382. return md5(json_encode($data));
  383. }
  384. public static function _msgHandle($content, $msg_type, $len = 16) {
  385. if ($msg_type > 0) { // 只处理文本
  386. return $content;
  387. }
  388. $source = Utils::decodeRC4($content);
  389. !$source && $source = $content;
  390. if (mb_strlen($source) > $len) {
  391. $source = mb_substr($source, 0, $len);
  392. }
  393. return Utils::encodeRC4($source);
  394. }
  395. public static function getMS() {
  396. return intval(microtime(true) * 1000);
  397. }
  398. /**
  399. * 自增session num
  400. * @author solu
  401. * @param $sessionId
  402. * @return int
  403. */
  404. public static function incrLastMsgNum($sessionId) {
  405. $objRedis = dwRedis::init(Eos::REDIS_SERV);
  406. // 存在原子性问题
  407. if ($objRedis->hExists(self::LAST_MSG_NUM_HASH, $sessionId)) {
  408. return $objRedis->hIncrBy(self::LAST_MSG_NUM_HASH, $sessionId, 1);
  409. }
  410. if (is_numeric($sessionId)) { // 群组
  411. $objGroupMsg = new GroupMsg();
  412. $c = $objGroupMsg->objTable->getCount(['group_id' => $sessionId]);
  413. } else {
  414. $objPersonMsg = new PersonMsg();
  415. $c = $objPersonMsg->objTable->getCount(['session_id' => $sessionId]);
  416. }
  417. $c += 1;
  418. return $objRedis->hIncrBy(self::LAST_MSG_NUM_HASH, $sessionId, $c);
  419. }
  420. /**
  421. * 获取session最新num
  422. * @author solu
  423. * @param $sessionId
  424. * @param null $objRedis
  425. * @return int
  426. */
  427. public static function getLastMsgNum($sessionId, $objRedis = null) {
  428. !$objRedis && $objRedis = dwRedis::init(Eos::REDIS_SERV);
  429. return $objRedis->hGet(self::LAST_MSG_NUM_HASH, $sessionId) ?: 0;
  430. }
  431. public static function setLastMsg($sessionId, $msgType, $content, $from, $name, $extInfo = []) {
  432. $objRedis = dwRedis::init(Eos::REDIS_SERV);
  433. $msgType != self::MSG_TYPE_TEXT && $content = '';
  434. $data = [
  435. 'msg_type' => $msgType,
  436. 'event_type' => $msgType == self::MSG_TYPE_EVENT ? $extInfo['event_type'] : '',
  437. 'content' => $content,
  438. 'from' => $from,
  439. 'name' => $name,
  440. 'nick_name' => User::getUserNameById($from),
  441. 'time' => self::getMS(),
  442. ];
  443. $objRedis->hSet(self::LAST_MSG_HASH, $sessionId, json_encode($data));
  444. }
  445. public static function getLastMsg($sessionId, $objRedis = null) {
  446. !$objRedis && $objRedis = dwRedis::init(Eos::REDIS_SERV);
  447. if ($objRedis->hExists(self::LAST_MSG_HASH, $sessionId)) {
  448. $json = $objRedis->hGet(self::LAST_MSG_HASH, $sessionId);
  449. return json_decode($json, true);
  450. }
  451. $isGroup = is_numeric($sessionId);
  452. if ($isGroup) { // 群组
  453. $objGroupMsg = new GroupMsg();
  454. $row = $objGroupMsg->objTable->getRow(['group_id' => $sessionId]);
  455. } else {
  456. $objPersonMsg = new PersonMsg();
  457. $row = $objPersonMsg->objTable->getRow(['session_id' => $sessionId]);
  458. }
  459. $data = null;
  460. if ($row) {
  461. $msg_type = $row['msg_type'];
  462. $content = '';
  463. $from = $row['from'];
  464. if ($msg_type == self::MSG_TYPE_TEXT) {
  465. $content = self::_msgHandle($row['msg'], $msg_type);
  466. }
  467. $nick_name = User::getUserNameById($from);
  468. $time = self::getMS();
  469. $data = compact('msg_type', 'content', 'from', 'nick_name', 'time');
  470. $objRedis->hSet(self::LAST_MSG_HASH, $sessionId, json_encode($data));
  471. }
  472. return $data;
  473. }
  474. public static function getToUser($from, $sessionId) {
  475. $_tmp = explode('-', $sessionId);
  476. foreach ($_tmp as $_uid) {
  477. if ($_uid != $from) {
  478. return $_uid;
  479. }
  480. }
  481. return 0;
  482. }
  483. }