SWOLE-based Microsoft Scavenger Login

With the popularity of WeChat, the scanner login method is more and more used by current applications.Because it doesn't need to remember the password, it's easy to log in as long as there's a micro-signal.WeChat's open platform already has the ability to support scanner login, but most people still use the public platform, so scanner login can only be achieved by itself.This is a temporary two-dimensional code with parameters based on the WeChat public platform and combines with Swoole's WebSocket service to implement scanner login.The general process is as follows:

  1. The client opens the login interface and connects to the WebSocket service
  2. The WebScoket service generates a two-dimensional code with parameters that is returned to the client
  3. Two-dimensional code with parameters displayed by user scanning
  4. WeChat server calls back scanner events and notifies the developer server
  5. Developer Server Notifies WebSocket Service
  6. WebSocket service notifies client of successful login

Connect to WebSocket Service

After installing Swoole, we need to use the WebSocket service.It's easy to create a new WebSocket service:

1 $server = new swoole_websocket_server("", 1099);
2 $server->on('open', function (swoole_websocket_server $server, $request) use ($config){
3     echo "server: handshake success with fd{$request->fd}\n";
4 });
6 $server->on('message', function (swoole_websocket_server $server, $frame) {
8 });


message callbacks are not really useful here because they are all sent from the service side, but one must be set.If you set a port number below 1024, you must have root privilege. The server remembers to go to the firewall to open the port.


Generating two-dimensional codes with parameters

After the successful connection of the client, the WebSocket service needs to generate a WeChat QR code with parameters to be returned to the client for display:

1 $server->on('open', function (swoole_websocket_server $server, $request) use ($config){
2     $app = Factory::officialAccount($config['wechat']);
3     $result = $app->qrcode->temporary($request->fd, 120);
4     $url = $app->qrcode->url($result['ticket']);
5     $server->push($request->fd, json_encode([
6         'message_type'    =>  'qrcode_url',
7         'url'       =>  $url
8     ]));
9 });


In the open callback, we generate a temporary two-dimensional code whose scene value is the file descriptor of the client connection, which guarantees the uniqueness of each client.The effective time is set to 120 seconds to prevent a two-dimensional code from being scanned multiple times.Message push must be json when it comes to the client for the client to handle.The client code is also simple:

1 const socket = new WebSocket('ws://');
2     socket.addEventListener('message', function (event) {
3         var data = JSON.parse(event.data);
4         if (data.message_type == 'qrcode_url'){
5             $('#qrcode').attr('src', data.url);
6         }
7     });



Callback Scavenger Event

After the client displays the QR code, the user needs to be prompted to sweep the code.For users to scan temporary 2D codes, WeChat triggers corresponding callback events, in which we need to handle the user's scavenging behavior.We need to use some parameters that WeChat passed in:

FromUserName sender account number (an OpenID)
MsgType message type, event
 Event event type, subscribe
 EventKey Event KEY value, qrscene_prefix, followed by parameter values of two-dimensional codes

One thing to note here is that EventKey, which WeChat has paid attention to scanner pushing, does not have the qrscene_ prefix, only if it does not focus on scanner then on it.

After receipt of the WeChat callback, we first need to do different processing according to different event types:

 1 if ($message['MsgType'] == 'event'){
 2     if ($message['Event'] == 'subscribe'){  //follow
 3         return $this->subscribe($message);
 4     }
 5     if ($message['Event'] == 'unsubscribe') {  //Remove Attention
 6         return $this->unsubscribe($message);
 7     }
 8     if ($message['Event'] == 'SCAN'){   //Focused on scanner
 9         return $this->scan($message);
10     }
11 }else{
12     return "Hello!Welcome SwooleWechat Scan Login";
13 }


Only one business logic that focuses on events is explained here, and the others are coded as needed:

 1 public function subscribe($message){
 2     $eventKey = intval(str_replace('qrscene_', '', $message['EventKey']));
 3     $openId = $message['FromUserName'];
 4     $user = $this->app->user->get($openId);
 5     $this->notify(json_encode([
 6         'type'  =>  'scan',
 7         'fd'    =>  $eventKey,
 8         'nickname'  =>  $user['nickname']
 9     ]));
10     $count = $this->count($openId);
11     $msgTemp = "%s,Login successful!\n This is your first%s Sign in again, have fun!";
12     return sprintf($msgTemp, $user['nickname'], $count);
13 }


EventKey here is essentially a client file descriptor that connects to a WebSocket, obtains the OPEN_ID of the scanner user, obtains user information based on the user's OPEN_ID, notifies the WebSocket service, and responds to a text message to WeChat.
One of the more troublesome points here is how to notify the WebSocket service. We know that the code for handling WeChat callbacks is not on the WebSocket service, so how do different servers communicate with each other?Swoole officially offers two solutions:

  1. Additional listening on a UDP port
  2. Using swoole_client as a client to access Server

Here we choose the second scenario, Swoole version 1.8 supports one Server to listen on multiple ports, and we add a new TCP port to the WebSocket service:

1 $tcp_server = $server->addListener('', 9999, SWOOLE_SOCK_TCP);
2 $tcp_server->set([]);
3 $tcp_server->on('receive', function ($serv, $fd, $threadId, $data) {
5 });


The primary Server is a WebSocket or Http protocol. The TCP port newly listened inherits the protocol settings of the primary Server by default. The set method must be invoked separately to set the new protocol before the new protocol can be enabled.

Then we can inform the WebSocket service during the scanner callback process:

1 public function notify($message){
2     $client = new swoole_client(SWOOLE_SOCK_TCP);
3     if (!$client->connect('', $this->config['notify_port'], -1)) {
4         return "connect failed. Error: {$client->errCode}\n";
5     }
6     $ret = $client->send($message);
7 }



Notify logon success

After the WebSocket service receives the notification of successful login, it can process the user information as needed, and then pass the user information to the client's browser to display the results. Remember the TCP port we just listened on?It can be handled in a receive event:

 1 $tcp_server->on('receive', function ($serv, $fd, $threadId, $data) {
 2     $data = json_decode($data, true);
 3     if ($data['type'] == 'scan'){
 4         $serv->push($data['fd'], json_encode([
 5             'message_type'    =>  'scan_success',
 6             'user'  =>  $data['nickname']
 7         ]));
 8     }
 9     $serv->close($fd);
10 });


Last logged in interface:
Mapping is too cumbersome. Go to http://wechat.sunnyshift.com/index.php to test the address.



The whole process is not difficult. The two main difficulties are the corresponding scanner users for connecting users and the communication between different servers. Our solution is to use the connected file descriptors as temporary QR scene values (where Redis can also be used to store mapping relationships) and listen for new TCP ports to receive notification messages.You can try it at http://wechat.sunnyshift.com/index.php, remember to turn it on with your computer.

Keywords: PHP QRCode JSON socket

Added by cspgsl on Sat, 23 Nov 2019 11:56:07 +0200