Session.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  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';
  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';
  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 = 100;
  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. $list[$k] = $v;
  156. }
  157. return $list;
  158. }
  159. public function getUserMap($user_ids) {
  160. $objUserInfo = new TableHelper('user_info', 'dw_chat');
  161. $datas = $objUserInfo->getAll(['user_id' => $user_ids], ['_field' => 'user_id, user_name, nick_name, cover_photo']);
  162. coverReplaceArrImage($datas, 'cover_photo');
  163. return arrayFormatKey($datas, 'user_id');
  164. }
  165. /**
  166. * 修改状态
  167. * @param $session_id
  168. * @param $newData
  169. * @param $ext;
  170. */
  171. public function updateState($session_id, $newData, $ext = []) {
  172. $where = compact('session_id');
  173. $where = array_merge($where, $ext);
  174. $newData['update_time'] = NOW;
  175. $newData['update_time_int'] = microtime(true) * 1000;
  176. $this->objTable->updateObject($newData, $where);
  177. }
  178. /**
  179. * 检测私聊session
  180. * @author solu
  181. * @param $from
  182. * @param $sessionId
  183. * @return array
  184. * @throws Exception
  185. */
  186. public function checkPersonSession($from, $sessionId) {
  187. $uids = explode('-', $sessionId);
  188. $uids = array_filter($uids, function ($v) {return $v > 0;});
  189. // if (count($uids) != 2 || !$this->objTable->getRow(['user_id' => $from, 'session_id' => $sessionId])) {
  190. // throw new Exception('session_id error', CODE_PARAM_ERROR);
  191. // }
  192. if (!in_array($from, $uids)) {
  193. throw new Exception('user not in session', CODE_NO_PERMITION);
  194. }
  195. $to = 0;
  196. foreach ($uids as $uid) {
  197. if ($uid != $from) {
  198. $to = $uid;
  199. break;
  200. }
  201. }
  202. return [$from, $to];
  203. }
  204. /**
  205. * 检测群session
  206. * @author solu
  207. * @param $from
  208. * @param $sessionId
  209. * @return bool
  210. * @throws Exception
  211. */
  212. public function checkGroupSession($from, $sessionId) {
  213. if (!$this->objTable->getRow(['user_id' => $from, 'session_id' => $sessionId])) {
  214. throw new Exception('user not in session', CODE_NO_PERMITION);
  215. }
  216. return true;
  217. }
  218. public static function getPersonSessionId($from, $to) {
  219. if ($from > $to) {
  220. return "{$to}-{$from}";
  221. } else {
  222. return "{$from}-{$to}";
  223. }
  224. }
  225. private function initPersonSession($from, $to) {
  226. $session_id = self::getPersonSessionId($from, $to);
  227. $num = $this->objTable->getCount(compact('session_id'));
  228. if ($num < 2) { // 单方面删除会话的情况
  229. // 插入双方的session
  230. $datas = [[
  231. 'user_id' => $from,
  232. 'session_id' => $session_id,
  233. 'is_group' => 0,
  234. ], [
  235. 'user_id' => $to,
  236. 'session_id' => $session_id,
  237. 'is_group' => 0,
  238. ]
  239. ];
  240. // 第一次初始化,需要初始化
  241. $this->objTable->addObjectsIfNoExist($datas);
  242. }
  243. }
  244. /**
  245. * 发送私聊消息
  246. * @author solu
  247. * @param $from
  248. * @param $sessionId
  249. * @param $msg_type
  250. * @param $msg
  251. * @param $noEvent
  252. * @return array
  253. * @throws Exception
  254. */
  255. public function sendPersonMsg($from, $sessionId, $msg_type, $msg, $noEvent = false) {
  256. list($from, $to) = $this->checkPersonSession($from, $sessionId);
  257. $this->initPersonSession($from, $to);
  258. $t = self::getMS();
  259. 0 == $msg_type && $msg = htmlentities($msg);
  260. $lastNum = self::incrLastMsgNum($sessionId);
  261. $data = [
  262. 'session_id' => $sessionId,
  263. 'from' => intval($from),
  264. 'to' => intval($to),
  265. 'msg_type' => $msg_type,
  266. 'msg' => $msg,
  267. 'create_time' => NOW,
  268. 'create_time_int' => $t,
  269. 'msg_num' => $lastNum,
  270. ];
  271. $data['hash'] = self::_genHash($data);
  272. $objPersonMsg = new TableHelper('person_msg', 'dw_chat');
  273. if (!$objPersonMsg->addObject($data)) {
  274. throw new Exception('send message error', CODE_NORMAL_ERROR);
  275. }
  276. // 更新发送人已读序号
  277. $this->updateState($sessionId, ['read_num' => $lastNum], ['user_id' => $from]);
  278. $name = User::getUserNameById($from);
  279. $content = self::_msgHandle($msg, $msg_type);
  280. $eventData = [
  281. 'type' => 'msg',
  282. 'msg_type' => $msg_type,
  283. 'from' => intval($from),
  284. 'to' => intval($to),
  285. 'name' => $name,
  286. 'nick_name' => User::getUserNameById($from),
  287. 'content' => $content,
  288. 'hash' => $data['hash'],
  289. 'timestamp' => $t,
  290. ];
  291. !$noEvent && ThirdApi::pushPersonEvent($to, $eventData);
  292. !$noEvent && ThirdApi::pushPersonEvent($from, $eventData);
  293. self::setLastMsg($sessionId, $msg_type, $content, $from, $name);
  294. $eventData['content'] = $msg;
  295. return $eventData;
  296. }
  297. /**
  298. * 发送群聊消息
  299. * @author solu
  300. * @param $from
  301. * @param $groupId
  302. * @param $msg_type
  303. * @param $msg
  304. * @param $noEvent
  305. * @return array
  306. * @throws Exception
  307. */
  308. public function sendGroupMsg($from, $groupId, $msg_type, $msg, $noEvent = false) {
  309. $userMap = null;
  310. if (!$this->objTable->getRow(['user_id' => $from, 'session_id' => $groupId])) {
  311. // 聊天就自动加入群
  312. $objGroup = new GroupInfo();
  313. $objGroup->joinGroup($from, $groupId);
  314. // 第一次发言,需要返回用户信息
  315. $userMap = $this->getUserMap($from);
  316. } else if ((new UserGroup())->isBlock($groupId, $from)) {
  317. throw new Exception('Banned', CODE_NORMAL_ERROR);
  318. }
  319. $t = self::getMS();
  320. 0 == $msg_type && $msg = htmlentities($msg);
  321. $lastNum = self::incrLastMsgNum($groupId);
  322. $data = [
  323. 'group_id' => intval($groupId),
  324. 'from' => $from,
  325. 'msg_type' => $msg_type,
  326. 'msg' => $msg,
  327. 'create_time' => NOW,
  328. 'create_time_int' => $t,
  329. 'msg_num' => $lastNum,
  330. ];
  331. $data['hash'] = self::_genHash($data);
  332. $objGroupMsg = new TableHelper('group_msg', 'dw_chat');
  333. if (!$objGroupMsg->addObject($data)) {
  334. throw new Exception('send message error', CODE_NORMAL_ERROR);
  335. }
  336. // 更新发送人已读序号
  337. $this->updateState($groupId, ['read_num' => $lastNum], ['user_id' => $from]);
  338. $content = self::_msgHandle($msg, $msg_type);
  339. $name = GroupInfo::getGroupNameById($groupId);
  340. $eventData = [
  341. 'type' => 'msg',
  342. 'msg_type' => $msg_type,
  343. 'from' => $from,
  344. 'name' => $name,
  345. 'nick_name' => User::getUserNameById($from),
  346. 'content' => $content,
  347. 'hash' => $data['hash'],
  348. 'timestamp' => $t,
  349. ];
  350. !$noEvent && ThirdApi::pushGroupEvent($groupId, $eventData);
  351. self::setLastMsg($groupId, $msg_type, $content, $from, $name);
  352. $eventData['content'] = $msg;
  353. $eventData['userMap'] = $userMap;
  354. return $eventData;
  355. }
  356. private static function _genHash($data) {
  357. return md5(json_encode($data));
  358. }
  359. public static function _msgHandle($content, $msg_type, $len = 16) {
  360. if ($msg_type > 0) { // 只处理文本
  361. return $content;
  362. }
  363. $source = Utils::decodeRC4($content);
  364. !$source && $source = $content;
  365. if (mb_strlen($source) > $len) {
  366. $source = mb_substr($source, 0, $len);
  367. }
  368. return Utils::encodeRC4($source);
  369. }
  370. public static function getMS() {
  371. return intval(microtime(true) * 1000);
  372. }
  373. /**
  374. * 自增session num
  375. * @author solu
  376. * @param $sessionId
  377. * @return int
  378. */
  379. public static function incrLastMsgNum($sessionId) {
  380. $objRedis = dwRedis::init(Eos::REDIS_SERV);
  381. // 存在原子性问题
  382. if ($objRedis->hExists(self::LAST_MSG_NUM_HASH, $sessionId)) {
  383. return $objRedis->hIncrBy(self::LAST_MSG_NUM_HASH, $sessionId, 1);
  384. }
  385. if (is_numeric($sessionId)) { // 群组
  386. $objGroupMsg = new GroupMsg();
  387. $c = $objGroupMsg->objTable->getCount(['group_id' => $sessionId]);
  388. } else {
  389. $objPersonMsg = new PersonMsg();
  390. $c = $objPersonMsg->objTable->getCount(['session_id' => $sessionId]);
  391. }
  392. $c += 1;
  393. return $objRedis->hIncrBy(self::LAST_MSG_NUM_HASH, $sessionId, $c);
  394. }
  395. /**
  396. * 获取session最新num
  397. * @author solu
  398. * @param $sessionId
  399. * @param null $objRedis
  400. * @return int
  401. */
  402. public static function getLastMsgNum($sessionId, $objRedis = null) {
  403. !$objRedis && $objRedis = dwRedis::init(Eos::REDIS_SERV);
  404. return $objRedis->hGet(self::LAST_MSG_NUM_HASH, $sessionId) ?: 0;
  405. }
  406. public static function setLastMsg($sessionId, $msgType, $content, $from, $name) {
  407. $objRedis = dwRedis::init(Eos::REDIS_SERV);
  408. $msgType != self::MSG_TYPE_TEXT && $content = '';
  409. $data = [
  410. 'msg_type' => $msgType,
  411. 'content' => $content,
  412. 'from' => $from,
  413. 'name' => $name,
  414. 'nick_name' => User::getUserNameById($from),
  415. 'time' => self::getMS(),
  416. ];
  417. $objRedis->hSet(self::LAST_MSG_HASH, $sessionId, json_encode($data));
  418. }
  419. public static function getLastMsg($sessionId, $objRedis = null) {
  420. !$objRedis && $objRedis = dwRedis::init(Eos::REDIS_SERV);
  421. if ($objRedis->hExists(self::LAST_MSG_HASH, $sessionId)) {
  422. $json = $objRedis->hGet(self::LAST_MSG_HASH, $sessionId);
  423. return json_decode($json, true);
  424. }
  425. $isGroup = is_numeric($sessionId);
  426. if ($isGroup) { // 群组
  427. $objGroupMsg = new GroupMsg();
  428. $row = $objGroupMsg->objTable->getRow(['group_id' => $sessionId]);
  429. } else {
  430. $objPersonMsg = new PersonMsg();
  431. $row = $objPersonMsg->objTable->getRow(['session_id' => $sessionId]);
  432. }
  433. $data = null;
  434. if ($row) {
  435. $msg_type = $row['msg_type'];
  436. $content = '';
  437. $from = $row['from'];
  438. if ($msg_type == self::MSG_TYPE_TEXT) {
  439. $content = self::_msgHandle($row['msg'], $msg_type);
  440. }
  441. $nick_name = User::getUserNameById($from);
  442. $time = self::getMS();
  443. $data = compact('msg_type', 'content', 'from', 'nick_name', 'time');
  444. $objRedis->hSet(self::LAST_MSG_HASH, $sessionId, json_encode($data));
  445. }
  446. return $data;
  447. }
  448. public static function getToUser($from, $sessionId) {
  449. $_tmp = explode('-', $sessionId);
  450. foreach ($_tmp as $_uid) {
  451. if ($_uid != $from) {
  452. return $_uid;
  453. }
  454. }
  455. return 0;
  456. }
  457. }