Session.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  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_TEXT = 0;
  10. const MSG_TYPE_IMAGE = 1;
  11. const MSG_TYPE_VIDEO = 2;
  12. const MSG_TYPE_AUDIO = 3;
  13. const MSG_TYPE_REDPACK = 4;
  14. const LAST_MSG_NUM_HASH = 'globals:last_msg_num_hash';
  15. const LAST_MSG_HASH = 'globals:last_msg_hash';
  16. /**
  17. * 填充会话列表
  18. * @param string $user_id 用户id
  19. * @param array $list 会话列表
  20. * @param int $currentGroupId 当前群id
  21. *
  22. * @return mixed
  23. */
  24. public function fillSession($user_id, $list, $currentGroupId) {
  25. $group_ids = [];
  26. $user_ids = [];
  27. foreach ($list as $item) {
  28. if ($item['is_group']) {
  29. $group_ids[] = $item['session_id'];
  30. } else {
  31. // 把自己的用户名换掉
  32. $user_ids[] = str_replace([$user_id, '-'], ['', ''], $item['session_id']);
  33. }
  34. }
  35. $objUserInfo = new TableHelper('user_info', 'dw_chat');
  36. $_field = 'user_id, user_name, nick_name, cover_photo';
  37. $users = $objUserInfo->getAll(['user_id' => $user_ids], compact('_field'));
  38. $users = arrayFormatKey($users, 'user_id');
  39. $objGroupInfo = new TableHelper('group_info', 'dw_chat');
  40. $_field = 'group_id, group_name, group_title, cover_photo';
  41. $groups = $objGroupInfo->getAll(['group_id' => $group_ids], compact('_field'));
  42. $groups = arrayFormatKey($groups, 'group_id');
  43. $userGroups = [];
  44. if ($currentGroupId) {
  45. $objUserGroup = new UserGroup();
  46. $userGroups = $objUserGroup->objTable->getAll(['user_id' => $user_ids, 'group_id' => $currentGroupId], ['_field' => 'user_id, group_id']);
  47. $userGroups = arrayFormatKey($userGroups, 'user_id', 'group_id');
  48. }
  49. $objRedis = dwRedis::init(Eos::REDIS_SERV);
  50. foreach ($list as $i => $item) {
  51. if ($item['is_group']) {
  52. $group = $groups[$item['session_id']];
  53. $item['name'] = $group['group_title'] ?: $group['group_name'];
  54. $item['cover_photo'] = $group['cover_photo'];
  55. } else {
  56. // 把自己的用户名换掉
  57. $key = str_replace([$user_id, '-'], ['', ''], $item['session_id']);
  58. $user = $users[$key];
  59. $item['name'] = $user['nick_name'];
  60. $item['cover_photo'] = $user['cover_photo'];
  61. $item['in_group'] = $userGroups[$key] ? 1 : 0; // 已经在群
  62. }
  63. $lastNum = self::getLastMsgNum($item['session_id']);
  64. $item['unread'] = max($lastNum - $item['read_num'], 0);
  65. $item['cover_photo'] = coverReplaceImage($item['cover_photo']);
  66. $item['last_msg'] = self::getLastMsg($item['session_id'], $objRedis);
  67. $list[$i] = $item;
  68. }
  69. return $list;
  70. }
  71. public function getMsgList($session_id, $read_hash, $load_type, $is_group) {
  72. $where2 = [];
  73. if ($is_group) {
  74. $_field = '`hash`, `from`, `msg`, `msg_type`, `state`, create_time_int, msg_num';
  75. $objMsg = new TableHelper('group_msg', 'dw_chat');
  76. $where2['group_id'] = $session_id;
  77. } else {
  78. $_field = '`hash`, `from`, `to`, `msg`, `msg_type`, `state`, create_time_int, msg_num';
  79. $objMsg = new TableHelper('person_msg', 'dw_chat');
  80. $where2['session_id'] = $session_id;
  81. }
  82. if ($read_hash) {
  83. $currentRow = $objMsg->getRow(['hash' => $read_hash], compact('_field'));
  84. $create_time_int = (int) $currentRow['create_time_int'];
  85. } else {
  86. $create_time_int = 0;
  87. }
  88. $_limit = 100;
  89. $maxNum = 1000;
  90. $need_clear = false;
  91. if ($load_type == 0) {
  92. // 正常加载
  93. if ($create_time_int > 0) {
  94. $_limit = $maxNum;
  95. // 需要判断是否存在过多未读消息
  96. $_where = "create_time_int > {$create_time_int}";
  97. $num = $objMsg->getCount($where2, compact('_where'));
  98. if ($num > $maxNum) {
  99. // 这种情况抛弃create_time_int...,消息太多了,只拿最新的1000条记录
  100. $create_time_int = 0;
  101. $need_clear = true;
  102. }
  103. }
  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', 'need_clear');
  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. $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. * @return array
  254. * @throws Exception
  255. */
  256. public function sendPersonMsg($from, $sessionId, $msg_type, $msg, $noEvent = false) {
  257. list($from, $to) = $this->checkPersonSession($from, $sessionId);
  258. $this->initPersonSession($from, $to);
  259. $t = self::getMS();
  260. 0 == $msg_type && $msg = htmlentities($msg);
  261. $lastNum = self::incrLastMsgNum($sessionId);
  262. $data = [
  263. 'session_id' => $sessionId,
  264. 'from' => intval($from),
  265. 'to' => intval($to),
  266. 'msg_type' => $msg_type,
  267. 'msg' => $msg,
  268. 'create_time' => NOW,
  269. 'create_time_int' => $t,
  270. 'msg_num' => $lastNum,
  271. ];
  272. $data['hash'] = self::_genHash($data);
  273. $objPersonMsg = new TableHelper('person_msg', 'dw_chat');
  274. if (!$objPersonMsg->addObject($data)) {
  275. throw new Exception('send message error', CODE_NORMAL_ERROR);
  276. }
  277. // 更新发送人已读序号
  278. $this->updateState($sessionId, ['read_num' => $lastNum], ['user_id' => $from]);
  279. $name = User::getUserNameById($from);
  280. $content = self::_msgHandle($msg, $msg_type);
  281. $eventData = [
  282. 'type' => 'msg',
  283. 'from' => $from,
  284. 'to' => strval($to),
  285. 'name' => $name,
  286. 'content' => $content,
  287. 'hash' => $data['hash'],
  288. 'timestamp' => $t,
  289. ];
  290. !$noEvent && ThirdApi::pushPersonEvent($to, $eventData);
  291. self::setLastMsg($sessionId, $msg_type, $content, $from, $name);
  292. $this->updateState($sessionId, []);
  293. $eventData['content'] = $msg;
  294. return $eventData;
  295. }
  296. /**
  297. * 发送群聊消息
  298. * @author solu
  299. * @param $from
  300. * @param $groupId
  301. * @param $msg_type
  302. * @param $msg
  303. * @param $noEvent
  304. * @return array
  305. * @throws Exception
  306. */
  307. public function sendGroupMsg($from, $groupId, $msg_type, $msg, $noEvent = false) {
  308. $userMap = null;
  309. if (!$this->objTable->getRow(['user_id' => $from, 'session_id' => $groupId])) {
  310. // 聊天就自动加入群
  311. $objGroup = new GroupInfo();
  312. $objGroup->joinGroup($from, $groupId);
  313. // 第一次发言,需要返回用户信息
  314. $userMap = $this->getUserMap($from);
  315. } else if ((new UserGroup())->isBlock($groupId, $from)) {
  316. throw new Exception('Banned', CODE_NORMAL_ERROR);
  317. }
  318. $t = self::getMS();
  319. 0 == $msg_type && $msg = htmlentities($msg);
  320. $lastNum = self::incrLastMsgNum($groupId);
  321. $data = [
  322. 'group_id' => intval($groupId),
  323. 'from' => $from,
  324. 'msg_type' => $msg_type,
  325. 'msg' => $msg,
  326. 'create_time' => NOW,
  327. 'create_time_int' => $t,
  328. 'msg_num' => $lastNum,
  329. ];
  330. $data['hash'] = self::_genHash($data);
  331. $objGroupMsg = new TableHelper('group_msg', 'dw_chat');
  332. if (!$objGroupMsg->addObject($data)) {
  333. throw new Exception('send message error', CODE_NORMAL_ERROR);
  334. }
  335. // 更新发送人已读序号
  336. $this->updateState($groupId, ['read_num' => $lastNum], ['user_id' => $from]);
  337. $content = self::_msgHandle($msg, $msg_type);
  338. $name = GroupInfo::getGroupNameById($groupId);
  339. $eventData = [
  340. 'type' => 'msg',
  341. 'msg_type' => $msg_type,
  342. 'from' => $from,
  343. 'name' => $name,
  344. 'content' => $content,
  345. 'hash' => $data['hash'],
  346. 'timestamp' => $t,
  347. ];
  348. !$noEvent && ThirdApi::pushGroupEvent($groupId, $eventData);
  349. self::setLastMsg($groupId, $msg_type, $content, $from, $name);
  350. $eventData['content'] = $msg;
  351. $eventData['userMap'] = $userMap;
  352. return $eventData;
  353. }
  354. private static function _genHash($data) {
  355. return md5(json_encode($data));
  356. }
  357. public static function _msgHandle($content, $msg_type, $len = 16) {
  358. if ($msg_type > 0) { // 只处理文本
  359. return $content;
  360. }
  361. $source = Utils::decodeRC4($content);
  362. !$source && $source = $content;
  363. if (mb_strlen($source) > $len) {
  364. $source = mb_substr($source, 0, $len);
  365. }
  366. return Utils::encodeRC4($source);
  367. }
  368. public static function getMS() {
  369. return intval(microtime(true) * 1000);
  370. }
  371. /**
  372. * 自增session num
  373. * @author solu
  374. * @param $sessionId
  375. * @return int
  376. */
  377. public static function incrLastMsgNum($sessionId) {
  378. $objRedis = dwRedis::init(Eos::REDIS_SERV);
  379. // 存在原子性问题
  380. if ($objRedis->hExists(self::LAST_MSG_NUM_HASH, $sessionId)) {
  381. return $objRedis->hIncrBy(self::LAST_MSG_NUM_HASH, $sessionId, 1);
  382. }
  383. if (is_numeric($sessionId)) { // 群组
  384. $objGroupMsg = new GroupMsg();
  385. $c = $objGroupMsg->objTable->getCount(['group_id' => $sessionId]);
  386. } else {
  387. $objPersonMsg = new PersonMsg();
  388. $c = $objPersonMsg->objTable->getCount(['session_id' => $sessionId]);
  389. }
  390. $c += 1;
  391. return $objRedis->hIncrBy(self::LAST_MSG_NUM_HASH, $sessionId, $c);
  392. }
  393. /**
  394. * 获取session最新num
  395. * @author solu
  396. * @param $sessionId
  397. * @param null $objRedis
  398. * @return int
  399. */
  400. public static function getLastMsgNum($sessionId, $objRedis = null) {
  401. !$objRedis && $objRedis = dwRedis::init(Eos::REDIS_SERV);
  402. return $objRedis->hGet(self::LAST_MSG_NUM_HASH, $sessionId) ?: 0;
  403. }
  404. public static function setLastMsg($sessionId, $msgType, $content, $from, $name) {
  405. $objRedis = dwRedis::init(Eos::REDIS_SERV);
  406. $msgType != self::MSG_TYPE_TEXT && $content = '';
  407. $data = [
  408. 'msg_type' => $msgType,
  409. 'content' => $content,
  410. 'from' => $from,
  411. 'name' => $name,
  412. ];
  413. $objRedis->hSet(self::LAST_MSG_HASH, $sessionId, json_encode($data));
  414. }
  415. public static function getLastMsg($sessionId, $objRedis = null) {
  416. !$objRedis && $objRedis = dwRedis::init(Eos::REDIS_SERV);
  417. if ($objRedis->hExists(self::LAST_MSG_HASH, $sessionId)) {
  418. $json = $objRedis->hGet(self::LAST_MSG_HASH, $sessionId);
  419. return json_decode($json, true);
  420. }
  421. $isGroup = is_numeric($sessionId);
  422. if ($isGroup) { // 群组
  423. $objGroupMsg = new GroupMsg();
  424. $row = $objGroupMsg->objTable->getRow(['group_id' => $sessionId]);
  425. } else {
  426. $objPersonMsg = new PersonMsg();
  427. $row = $objPersonMsg->objTable->getRow(['session_id' => $sessionId]);
  428. }
  429. $data = null;
  430. if ($row) {
  431. $msg_type = $row['msg_type'];
  432. $content = '';
  433. $from = $row['from'];
  434. if ($msg_type == self::MSG_TYPE_TEXT) {
  435. $content = self::_msgHandle($row['msg'], $msg_type);
  436. }
  437. if ($isGroup) {
  438. $name = GroupInfo::getGroupNameById($row['group_id']);
  439. } else {
  440. $name = User::getUserNameById($from);
  441. }
  442. $data = compact('msg_type', 'content', 'from', 'name');
  443. $objRedis->hSet(self::LAST_MSG_HASH, $sessionId, json_encode($data));
  444. }
  445. return $data;
  446. }
  447. }