Session.php 19 KB

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