Skip to content

Development Bank consumer SMS automatically record expenditure data, with Android and PHP

CJ edited this page Oct 9, 2017 · 1 revision

Do not know if you usually remember, anyway, I do not remember. Recently want to know how much money usually spent, such as a trend to do a look at the expenditure, the way to look at the expenditure details.

As an engineer, look at the consumer message is not a cool thing. If you can record every pen in the database, this thing looks cool. Do not use SELECT to query the consumer list, or use the GROUP sum to pay for each period of time.

Thinking is the case, the banks generally have consumer SMS reminder, analysis of the contents of the message to get every record of consumption. The development of an Android App, received a text message after the message sent to the PHP back-end program analysis and storage. I have an Andrews standby machine, in addition to analysis of expenditure, but also all the other SMS sent to my mailbox.

First look at the following database to save the time and amount of expenditure, the amount of units by sub-preservation.

mysql> select * from payments;
+----+------+-------+---------------------+---------------------+---------------------+
| id | bank | money | charge_time         | created_at          | updated_at          |
+----+------+-------+---------------------+---------------------+---------------------+
|  1 |      |  6000 | 2017-10-02 22:04:00 | 2017-10-05 00:02:56 | 2017-10-05 00:02:56 |
|  2 |      |  6000 | 2017-10-02 22:04:00 | 2017-10-05 00:12:29 | 2017-10-05 00:12:29 |
|  3 |      |  6000 | 2017-10-02 22:04:00 | 2017-10-05 00:15:06 | 2017-10-05 00:15:06 |
|  4 |      |  6000 | 2017-10-02 22:04:00 | 2017-10-05 00:15:14 | 2017-10-05 00:15:14 |
|  5 |      |    98 | 2017-10-05 08:28:00 | 2017-10-05 00:28:33 | 2017-10-05 00:28:33 |
|  6 |      |   351 | 2017-10-05 08:29:00 | 2017-10-05 00:29:19 | 2017-10-05 00:29:19 |
|  7 |      |    98 | 2017-10-05 08:33:00 | 2017-10-05 00:33:37 | 2017-10-05 00:33:37 |
|  8 |      |  3901 | 2017-10-05 08:35:00 | 2017-10-05 00:36:15 | 2017-10-05 00:36:15 |
|  9 |      |  1000 | 2017-10-06 06:25:00 | 2017-10-05 22:25:31 | 2017-10-05 22:25:31 |
| 10 |      |  4300 | 2017-10-07 04:17:00 | 2017-10-06 20:17:31 | 2017-10-06 20:17:31 |
| 11 |      | 17800 | 2017-10-07 05:48:00 | 2017-10-06 21:48:42 | 2017-10-06 21:48:42 |
| 12 |      |  2340 | 2017-10-07 07:09:00 | 2017-10-06 23:09:36 | 2017-10-06 23:09:36 |
| 13 |      |   450 | 2017-10-08 00:12:00 | 2017-10-07 16:12:16 | 2017-10-07 16:12:16 |
| 14 |      |  2700 | 2017-10-08 00:30:00 | 2017-10-07 16:30:28 | 2017-10-07 16:30:28 |
| 15 |      |  1100 | 2017-10-08 01:58:00 | 2017-10-07 17:58:37 | 2017-10-07 17:58:37 |
| 16 |      | 11600 | 2017-10-08 01:53:00 | 2017-10-07 17:59:00 | 2017-10-07 17:59:00 |
| 17 |      |  2800 | 2017-10-08 04:52:00 | 2017-10-07 20:52:19 | 2017-10-07 20:52:19 |
+----+------+-------+---------------------+---------------------+---------------------+
17 rows in set (0.00 sec)

Mail to receive text messages

Frameworks

Because the backend just provides an interface to save the expense and send the message, so the frame chooses the scribe lumen, no choice laravel. Send mail with symfony/swiftmailer. As the mail transmission process usually takes a long time, do not want the interface to the Android client request Hold too long time, so use the mqk/mqk background background task.

MQK is a pure PHP multi-process background task processing framework, the client uses K :: invoke ('Mailer', 'Subject', '[email protected]') to initiate an asynchronous call. MQK will enter the task queue, and in the background of the process of independent implementation. More suitable for mail to send such a scene.

Here I use the MQK another feature: the event mechanism, SMS interface to receive a message after the distribution of an event to the MQK.

The event mechanism is equivalent to the observer mode, and is particularly suitable for decoupling the code. In addition MQK uses a fixed process pool, and other message queue components are different. The general Message Queuing component starts a new process when the message arrives and is destroyed after the task is completed, with a lot of overhead when the message throughput is large.

MQK and php-fpm similar to the use of the process management mechanism to start a fixed number of processes, the number of processes is not enough time to go to the dynamic adjustment.

https://github.com/imcj/mqk 是MQK项目的地址,用 composer require mqk/mqk 安装。

  1. Define the sms storage router
# routes/web.php

$router->post('/', 'InboxController@sms');
  1. Controller K::dispatch is used to dispatch the event, message.in is the event name. MessageInEvent is the event that defines App\Events\MessageInEvent
class InboxController
{
    public function sms(Request $request)
    {
        $sender = $request->get("sender");
        $message = $request->get("message");

        \K::dispatch("message.in", new MessageInEvent($sender, $message));
    }
}
  1. Define MessageInEvent
# app/Events/MessageInEvent.php
class MessageInEvent extends \Symfony\Component\EventDispatcher\Event
{
    public $sender;
    public $message;

    public function __construct($sender, $message)
    {
        $this->sender = $sender;
        $this->message = $message;
    }
}
  1. Listener MessageIn event

\K::addListener listen message.in event,Receive text messages to analyze the time and amount, and then save. Finally, use the Swift Mailer to forward the message to my mailbox.

# /bootstrap.php
\K::addListener("message.in", function(MessageInEvent $event) {
    $sender = $event->sender;
    $message = $event->message;

    $financeMessage = new Message();
    $payment = $financeMessage->extract($message);

    if ($payment) {
        $payment->save();
    }

    $transport = (new \Swift_SmtpTransport(getenv("SMTP_HOST"), (int)getenv("SMTP_PORT"), 'ssl'))
        ->setUsername(getenv("SMTP_USER"))
        ->setPassword(getenv("SMTP_PASSWORD"));

    $mailer = new \Swift_Mailer($transport);
    $message = (new \Swift_Message("SMS from {$sender}"))
        ->setFrom(['[email protected]' => 'weicongju'])
        ->setTo(['[email protected]' => 'weicongju'])
        ->setBody($message);

    $mailer->send($message);
});

Using qqq

The above lumen project has put the source code to github on, and only need to clone down and deploy it can be used.

Download source code

$ git clone https://github.com/imcj/qqq 

Install dependencies

$ composer install

Config database and SMTP

Edit .env file.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=data
DB_USERNAME=root
DB_PASSWORD=123456

SMTP_HOST=smtp.qq.com
SMTP_PORT=465
SMTP_USER=
SMTP_PASSWORD=

QUEUE_DRIVER=array

Deploy database schema

$ php artisan migate

Start the server

$ php -S localhost:8000 -t public

Android

Android full source on https://github.com/imcj/QQQAndroid

iOS can not listen to SMS, can only use an Android backup machine. The idea is android.provider / Telephony.SMS_RECEIVED Intent to receive text messages.

Edit config file

from app/src/main/assets/config.properties.example copy to app/src/main/asset/config.properties

No sentry can apply for and register a project at https://getsentry.com

app/src/main/assets/config.properties

apiURL=New API URL
sentry=Sentry DSN

Config SMS Receiver

Config IncomingBroadcastReceiver class as SMS Receiver 。

<application>
	<receiver android:permission="android.permission.BROADCAST_SMS" android:name=".IncomingBroadcastReceiver" android:enabled="true" android:exported="true">
            <intent-filter android:priority="2147483647">
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
            </intent-filter>
        </receiver>
</application>

Check incoming messages in IncomingBroadcastReceiver. Send the message to HTTPGateway.sendMessage`. The interface received by SMS is used to send mail and write records asynchronously with MQK. The response time of the interface is only 40ms.

public class IncomingBroadcastReceiver extends BroadcastReceiver {

    HTTPGateway gateway;

    @Override
    public void onReceive(Context context, Intent intent) {

        final Bundle bundle = intent.getExtras();
        try {
            if (null == bundle)
                return;
            final Object[] objects = (Object[]) bundle.get("pdus");
            for (int i = 0, size = objects.length; i < size; i++) {
                SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) objects[i]);
                String phoneNumber = smsMessage.getDisplayOriginatingAddress();
                String body = smsMessage.getMessageBody();

                gateway.message(phoneNumber, body);

                Log.i("receive", phoneNumber + " " + body);
            }
        } catch (Exception e) {
            Log.e("Receive", e.getMessage());
        }

        Log.i("info", "On receive");
    }
}

Android crash restart

Taking into account the inexplicable circumstances Android will crash, add an Android exception after the restart function. Set the default exception handling, abnormal time to restart yourself.

Application class

<application
    ...
    android:name=".QQQApplication">
</application>

QQQApplication catch error and restart

public class QQQApplication extends Application {
    private static QQQApplication instance;

    @Override
    public void onCreate() {
        super.onCreate();

        instance = this;
        Thread.setDefaultUncaughtExceptionHandler(restartHandler);
    }

    private Thread.UncaughtExceptionHandler restartHandler = new Thread.UncaughtExceptionHandler() {

        public void uncaughtException(Thread thread, Throwable ex) {
            restartApp();
        }
    };

    public void restartApp() {
        Intent intent = new Intent(instance,MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        instance.startActivity(intent);
        android.os.Process.killProcess(android.os.Process.myPid());
    }
}

Use SentryAndroid to catch exceptions

Sentry is a platform-wide exception capture software that sends error messages to Sentry backstage when software is abnormal to track errors when problems occur

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
Context ctx = this.getApplicationContext();
        // Use the Sentry DSN (client key) from the Project Settings page on Sentry
        String sentryDsn = "https://[key]:[secret]@sentry.io/[id]?options";
        Sentry.init(sentryDsn, new AndroidSentryClientFactory(ctx));
    }
}