ComplexLogin.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. <?php
  2. /**
  3. * 聚合渠道登录控制器
  4. */
  5. namespace app\api\controller\v1;
  6. use app\api\controller\Api;
  7. use app\common\library\RsaSign;
  8. use app\common\model\ComplexLoginlog;
  9. use app\common\model\ComplexChannelModel;
  10. use app\common\model\ComplexMembers;
  11. use app\common\logic\Complex;
  12. use app\common\library\JwtToken;
  13. use think\Db;
  14. use think\Cache;
  15. use think\Exception;
  16. use think\Config;
  17. use app\common\model\PolychannelGame;
  18. use think\Env;
  19. use think\Request;
  20. class ComplexLogin extends Api
  21. {
  22. protected $noNeedLogin = ['index'];
  23. // ## TODO:每次新渠道,当前两个必须要添加
  24. // 需要进行登录验证的渠道标识
  25. protected $_neekCheckChannel = ['mw', 'tss', 'wanxiong', 'haofan', 'haipai', 'jiuqu', 'haofanceshi', 'onesixnn', 'ailequ', 'jiuquxin', 'jiandao', 'fiveseven', 'xingmai', 'haiquyou', 'naozhewan', 'lizi', 'zhaosy', 'yehua', 'shenqi', 'aiyou', 'qilin', 'yiyou', 'jule', 'yueyou', 'xinyou', 'simo', 'weilian', 'sanjiu', 'ledao', 'ledaoios', 'dangle', 'youxiyou', 'jlyx', 'tlyx', 'jcyx', 'test', 'kuaishou', 'pili', 'juheng', 'quchen', 'twothreethree', 'rrtv', 'simoh5', 'gyln', 'youchen', 'congyu', 'bhy', 'kshy', 'yinhu', 'peiqi', 'jywan', 'jywanh5'];
  26. // H5渠道游戏使用渠道逻辑处理
  27. protected $h5_handle_channel = ['zhaosy'];
  28. // 扩展参数
  29. protected $_extparam = [];
  30. public function _initialize()
  31. {
  32. parent::_initialize();
  33. }
  34. // 渠道效验
  35. public function getSpecialParam()
  36. {
  37. $prefix = strtolower($this->input('channel_mark')); // 渠道标识
  38. $gameid = strtolower($this->input('gameid')); // 游戏id
  39. $channelModel = new ComplexChannelModel;
  40. if (!($channel_id = $channelModel->where(['status' => 1, 'mark' => $prefix])->value('id'))) {
  41. $this->jsonResult('', -201, '渠道信息不存在');
  42. }
  43. if (!$polyChannelGame = (new PolychannelGame())->alias('cpg')->join('cy_game cg', 'cpg.game_id=cg.id')->field('cpg.*, cg.type as game_type')->where(['cpg.channel_id' => $channel_id, 'cpg.game_id' => $gameid])->find()) {
  44. $this->jsonResult('', -202, '渠道游戏不存在.');
  45. }
  46. // 判断是否有做登录验证的渠道
  47. if (in_array($prefix, $this->_neekCheckChannel)) {
  48. try {
  49. $sentence = ucfirst($prefix);
  50. $className = 'app\api\complex\\' . $sentence;
  51. $model = new $className;
  52. // H5 渠道游戏,单独处理
  53. if(in_array($prefix, $this->h5_handle_channel) && $polyChannelGame['game_type'] == 3){
  54. $result = $model->getSpecialParamH5($this->input, $polyChannelGame);
  55. }else{
  56. $result = $model->getSpecialParam($this->input, $polyChannelGame);
  57. }
  58. if ($result) {
  59. // 渠道效验判断,针对部分渠道的后台配置qmKey需要
  60. if (!empty($polyChannelGame['param']) && $this->input('device') == '2') {
  61. if ($paramArr = json_decode($polyChannelGame['param'], true)) {
  62. $qmKey = $this->input('qmKey'); // 游戏id
  63. if (isset($paramArr['qmKey'])) {
  64. if ($paramArr['qmKey'] != $qmKey || empty($qmKey)) {
  65. curlDD("渠道 " . $sentence . '@用户账号: ' . $result . ' 破解Android加固!', Env::get('dingtalk.warning_url'));
  66. $this->jsonResult('', -221, '当前渠道不可用!');
  67. }
  68. }
  69. }
  70. }
  71. $this->jsonResult(['cuserid' => $result], 1, '获取成功');
  72. } else {
  73. $msg = "登陆失效或二次验证失败";
  74. // 优化提示判断
  75. if(!empty($result['code']) && $result['code'] < 0){
  76. $msg .= ", (c_err:{$result['code']})";
  77. }
  78. $this->jsonResult('', -222, $msg);
  79. }
  80. } catch (Exception $e) {
  81. log_message("渠道-" . $sentence . '-二次效验失败:' . $e->getCode() . ":" . $e->getMessage() . '; ' . $e->getFile() . ':' . $e->getLine(), 'error', LOG_PATH . "complexLoginlog/{$prefix}/");
  82. curlDD("渠道-" . $sentence . '-二次效验失败:'. $e->getCode() . ":" . $e->getMessage() . '; ' . $e->getFile() . ':' . $e->getLine(), Env::get('dingtalk.notic_url'));
  83. $this->jsonResult('', -233, '二次登录验证失败');
  84. }
  85. } else {
  86. $this->jsonResult('', 0, '渠道未开启效验');
  87. }
  88. }
  89. // 登录
  90. public function index()
  91. {
  92. $channelModel = new ComplexChannelModel;
  93. $complexMembersModel = new ComplexMembers;
  94. $complexLogic = new Complex;
  95. $prefix = strtolower($this->input('channel_mark')); // 渠道标识
  96. $complex_username = trim($this->input('username')); // 聚合用户名
  97. $imeil = $this->input('imeil'); // 手机imeil码
  98. $device = $this->input('device'); // 注册设备来源,1为PC,2为安卓,3为苹果
  99. $gameid = $this->input('gameid'); // 游戏ID
  100. // $channelToken = $this->input('channelToken');
  101. $userId = $this->input('user_id', ''); // 渠道效验接口 返回的ID
  102. if (!($channel_id = $channelModel->where(['status' => 1, 'mark' => $prefix])->value('id'))) {
  103. $this->jsonResult('', -120, '渠道信息不存在');
  104. }
  105. if (!$polyChannelGame = (new PolychannelGame())->alias('cpg')->join('cy_game cg', 'cpg.game_id=cg.id')->field('cpg.*, cg.type as game_type')->where(['cpg.channel_id' => $channel_id, 'cpg.game_id' => $gameid])->find()) {
  106. $this->jsonResult('', -121, '渠道游戏不存在.');
  107. }
  108. //游戏ID不能为空
  109. if (empty($gameid)) {
  110. $this->jsonResult('', -122, '游戏ID不能为空');
  111. }
  112. //游戏ID不能为空
  113. if (empty($device)) {
  114. $this->jsonResult('', -123, '设备类型不能为空');
  115. }
  116. // ## IOS 加固效验
  117. if (!empty($polyChannelGame['param']) && $this->input('device') == '3' && !empty($this->input('qmversion'))) {
  118. $param = json_decode($polyChannelGame['param'], true);
  119. if (!empty($param['jg_key'])) {
  120. $jg_key = $this->input('jg_key'); //IOS 加固参数
  121. if (empty($jg_key)) {
  122. $this->jsonResult('{}', 126, '当前渠道不可用!');
  123. }
  124. $username = $this->input('username'); // username
  125. $rsa_res = (new RsaSign())->openssl_private_decrypt($jg_key);
  126. $ios_sign_key = $param['jg_key'] . $userId;
  127. if ($ios_sign_key !== $rsa_res) {
  128. curlDD("渠道 " . $prefix . '@聚合账户: ' . $username . ' 破解IOS加固!', Env::get('dingtalk.warning_url'));
  129. $this->jsonResult('{}', 126, '当前渠道不可用!');
  130. }
  131. }
  132. }
  133. // ## 渠道登录效验
  134. $result = [];
  135. if (in_array($prefix, $this->_neekCheckChannel)) {
  136. // $paramArr = [
  137. // 'username' => trim($this->input('username')) ,
  138. // 'imeil' => $this->input('imeil') ,
  139. // 'device' => $this->input('device') ,
  140. // 'gameid' => $this->input('gameid') ,
  141. // 'channel_mark' => strtolower($this->input('channel_mark')) ,
  142. // 'channelToken' => $this->input('channelToken') ,
  143. // ];
  144. try {
  145. $sentence = ucfirst($prefix);
  146. $className = 'app\api\complex\\' . $sentence;
  147. $model = new $className;
  148. // H5 渠道游戏,单独处理
  149. if(in_array($prefix, $this->h5_handle_channel) && $polyChannelGame['game_type'] == 3){
  150. $result = $model->checkLoginH5($this->input, $polyChannelGame);
  151. }else{
  152. $result = $model->checkLogin($this->input, $polyChannelGame);
  153. }
  154. // 默认判断
  155. if (!$result) {
  156. $this->jsonResult('', -131, "登录验证失败");
  157. }
  158. // 优化提示判断
  159. if(!empty($result['code']) && $result['code'] < 0){
  160. $msg = "登录验证失败, (c_err:{$result['code']})";
  161. $this->jsonResult('', -132, $msg);
  162. }
  163. // 检查是否有 setParam 方法
  164. if (method_exists($model, 'setParam')) {
  165. $this->_extparam = $model->setParam($this->input);
  166. }
  167. } catch (Exception $e) {
  168. log_message("渠道-" . $sentence . '-登陆效验失败:' . $e->getCode() . ":" . $e->getMessage() . '; ' . $e->getFile() . ':' . $e->getLine(), 'error', LOG_PATH . "complexLoginlog/{$prefix}/");
  169. curlDD("渠道-" . $sentence . '-登陆效验失败-渠道登录回调:msg=' . $e->getMessage() .' - input:'. json_encode($this->input), Env::get('dingtalk.notic_url'));
  170. $this->jsonResult('', -130, '登录验证异常.');
  171. }
  172. // $className = 'app\api\controller\complex\\' . $sentence.'Login';
  173. // $model_c = new $className;
  174. // $model_c->setParam($paramArr);
  175. // $resArr = $model_c->indexcheck();
  176. // log_message('xmw--get_http_response()--7-END--'.json_encode($resArr),'log',LOG_PATH . 'complexLoginlog/');
  177. // // 判断是否验证成功
  178. // if (!$resArr['code']){
  179. // $this->jsonResult('',0,$resArr['msg']);
  180. // }
  181. // $this->_extparam = $resArr['extparam'];
  182. // // 参数重置
  183. // log_message('xmw--get_http_response()--7.5-END--'.count($resArr['data']),'log',LOG_PATH . 'complexLoginlog/');
  184. // if (count($resArr['data'])){
  185. // log_message('xmw--get_http_response()--8-END--'.count($resArr['data']),'log',LOG_PATH . 'complexLoginlog/');
  186. // foreach ($resArr['data'] as $k=>$v){
  187. // log_message('xmw--get_http_response()--9-END--'.$k.'----'.$v,'log',LOG_PATH . 'complexLoginlog/');
  188. // $this->setInput($k,$v);
  189. // }
  190. // unset($v);
  191. // }
  192. }
  193. if (empty($complex_username)) {
  194. $this->jsonResult('', -140, '用户名不能为空');
  195. }
  196. if (empty($imeil)) {
  197. $this->jsonResult('', -141, '设备imeil异常');
  198. }
  199. if (empty($prefix)) {
  200. $this->jsonResult('', -142, '渠道标识不能为空');
  201. }
  202. if ($complexLogic->isFrozen($channel_id, $gameid, 'login')) {
  203. //根据预付款进行登录冻结判断还未加
  204. $this->jsonResult('', -143, '您所在渠道已经被禁止登录此游戏');
  205. } elseif (in_array($prefix, Config::get('complex_disable_login')['channel_mark']) && in_array($gameid, Config::get('complex_disable_login')['game_id'])) {
  206. //特殊的渠道和游戏禁止登录
  207. $this->jsonResult('', -144, '您所在渠道已经被禁止登录此游戏');
  208. }
  209. // 启动事务
  210. Db::startTrans();
  211. try {
  212. //nw_complex_members表中用户信息
  213. $userinfo = $complexMembersModel->where(['complex_username' => $complex_username, 'channel_id' => $channel_id, 'gameid' => $gameid])->find();
  214. //用户信息已存在,登录
  215. if (!empty($userinfo)) {
  216. $loginInfo = $this->loginProcess($userinfo, $gameid, $imeil, $device, $channel_id);
  217. $memberSaveData = ['login_time' => NOW_TIMESTAMP];
  218. if ($userId) {
  219. $memberSaveData['complex_id'] = $userId;
  220. }
  221. if (!empty($this->_extparam)) {
  222. $memberSaveData['extparam'] = json_encode($this->_extparam);
  223. }
  224. //更新最后登录时间
  225. $complexMembersModel->update($memberSaveData, ['id' => $userinfo['id']]);
  226. $mg_username = $userinfo['mg_username']; // 我方用户名
  227. } //用户信息不存在,注册
  228. else {
  229. //根据预付款进行注册冻结判断还未加
  230. if ($complexLogic->isFrozen($channel_id, $gameid, 'register')) {
  231. throw new Exception("您所在渠道已经被禁止注册此游戏");
  232. }
  233. $memberData = [
  234. 'complex_username' => $complex_username,
  235. 'device' => $device,
  236. 'gameid' => $gameid,
  237. 'imei' => $imeil,
  238. 'channel_id' => $channel_id,
  239. 'reg_time' => NOW_TIMESTAMP,
  240. 'login_time' => NOW_TIMESTAMP,
  241. 'ip' => request()->ip()
  242. ];
  243. if ($userId) {
  244. $memberData['complex_id'] = $userId;
  245. }
  246. if (!empty($this->_extparam)) {
  247. $memberData['extparam'] = json_encode($this->_extparam);
  248. }
  249. $userinfo = $complexMembersModel->create($memberData);
  250. // ## 游戏支持双端互通
  251. $pre_uid = $userinfo['id'];
  252. if(!empty($result['user_id'])){
  253. $pre_uid = $result['user_id'];
  254. }
  255. $mg_username = $prefix . '___' . $pre_uid . 'z'; // 我方用户名
  256. $complexMembersModel->update(['mg_username' => $mg_username], ['id' => $userinfo['id']]);
  257. $memberData['id'] = $userinfo['id'];
  258. $memberData['mg_username'] = $mg_username;
  259. $loginInfo = $this->loginProcess($memberData, $gameid, $imeil, $device, $channel_id);
  260. }
  261. $sign_str = "username={$mg_username}&appkey=" . $this->appInfo['appkey'] . "&logintime=" . NOW_TIMESTAMP;
  262. $sign = md5($sign_str); //这个暂时好像没什么用,只是参考旧SDK的
  263. // 提交事务
  264. Db::commit();
  265. } catch (\Exception $e) {
  266. // 回滚事务
  267. Db::rollback();
  268. $this->jsonResult('', -110, '登录失败! ' . $e->getMessage());
  269. }
  270. $result = [
  271. 'user_id' => (string)$userinfo['id'],
  272. 'username' => $mg_username,
  273. 'token' => $loginInfo['token'],
  274. 'logintime' => NOW_TIMESTAMP,
  275. 'sign' => $sign,
  276. 'extparam' => $this->_extparam
  277. ];
  278. if (isset($loginInfo['jwt_token'])) {
  279. $result['jwt_token'] = $loginInfo['jwt_token'];
  280. }
  281. $this->jsonResult($result, 1, '登录成功');
  282. exit;
  283. }
  284. /**
  285. * 用户登录公共部分处理
  286. *
  287. * @param $userinfo : array 用户信息数组
  288. * @param $gameid : int 游戏ID
  289. * @param $imeil : string 手机imei码
  290. * @param $device : int 注册设备来源
  291. * @param $channelId : int 当前登录的渠道ID
  292. *
  293. * @return $token: string 用户登录token
  294. */
  295. private function loginProcess($userinfo, $gameid, $imeil, $device, $channelId)
  296. {
  297. $loginlogModel = new ComplexLoginlog();
  298. $logData['userid'] = $userinfo['id'];
  299. $logData['gameid'] = $gameid;
  300. $logData['imei'] = $imeil;
  301. $logData['channel_id'] = $channelId;
  302. $logData['login_time'] = NOW_TIMESTAMP;
  303. $logData['ip'] = request()->ip();
  304. $loginlogModel->insert($logData);
  305. //token的随机码
  306. $token_random = random(8);
  307. $loginFailureTime = 60 * 60 * 24 * 30;
  308. // 登录态 token 为登录标识
  309. $token = auth_code($userinfo['id'] . '|' . $userinfo['mg_username'] . '|' . $gameid . '|' . $userinfo['mg_username'] . '|' . $token_random . '|cm', "ENCODE", Env::get('auth_key'));
  310. // $imeil 做为唯一登录标识
  311. Cache::store('default')->set($token, $imeil, $loginFailureTime);
  312. //redis中玩家登录安全控制的随机码
  313. Cache::store('default')->set('token|cm|' . $userinfo['id'] . '|' . $gameid, $token_random, $loginFailureTime);
  314. $returnData = ['token' => $token];
  315. // ## JWT:仅版本号大于 v3.6.8 的客户端返回 ##
  316. if (!empty($this->version) && version_compare($this->version, '3.6.8', '>')) {
  317. $jwtKey = trim((string) Env::get('jwt.jwt_key', ''));
  318. $jwtExpTime = (int) $loginFailureTime;
  319. $returnData['jwt_token'] = '';
  320. if ($jwtKey === '' || $jwtExpTime <= 0) {
  321. trace('JWT配置缺失或错误,请检查jwt.jwt_key', 'error');
  322. } else {
  323. $jwtIssuedAt = time();
  324. $jwtUserData = [
  325. 'member_id' => $userinfo['id'],
  326. 'mg_username' => $userinfo['mg_username'],
  327. 'game_id' => $gameid,
  328. 'complex_id' => $channelId,
  329. 'iat' => $jwtIssuedAt,
  330. 'exp' => $jwtIssuedAt + $jwtExpTime,
  331. ];
  332. $returnData['jwt_token'] = JwtToken::encode($jwtUserData, $jwtKey);
  333. }
  334. }
  335. return $returnData;
  336. }
  337. /**
  338. * 获取游戏配置参数(有些游戏有多个参数时需要特殊处理)
  339. * @param $game_id 游戏id
  340. * @param $channel_mark 渠道标识
  341. * @param string $data 返回的数据
  342. * @param string $code 返回的code
  343. * @param string $msg 返回的msg
  344. * @return mixed
  345. */
  346. // public function getGameParam($game_id,$channel_mark,$data='',$code='0',$msg='该游戏的验签参数还未设置'){
  347. // $polyChannelGameModel = new PolychannelGame;
  348. // $param = $polyChannelGameModel->alias('a')
  349. // ->join('nw_channel b','b.id = a.channel_id')
  350. // ->where(['a.game_id'=>$game_id,'b.mark'=>$channel_mark])
  351. // ->value('a.param');
  352. //
  353. // if(empty($param)){
  354. // log_message('后台渠道游戏中,用于该游戏的验签参数还未设置,game_id='.$game_id.',渠道标识='.$channel_mark,'error',LOG_PATH . '../complexPaylog/');
  355. //
  356. // $this->jsonResult($data,$code,$msg);
  357. // }
  358. //
  359. // $param = unserialize($param);
  360. // return $param;
  361. // }
  362. /**
  363. * 获取游戏指定的用户名连接符
  364. *
  365. * @param int $gameid 游戏ID
  366. * @return string
  367. */
  368. // public function get_username_connector($gameid) {
  369. // $specify = ['116','173','201'];
  370. //
  371. // if ( in_array($gameid, $specify) ) {
  372. // return '#';
  373. // } else {
  374. // return '___';
  375. // }
  376. // }
  377. }