German (Deutsch) translation by Wei Zhang (you can also view the original English article)

Dieses Tutorial ist Teil der Building Your Startup with PHP-Serie auf Envato Tuts +. In dieser Serie führe ich Sie durch die Einführung eines Start-ups von der Idee zur Realität mit meiner Meeting-Planer-App als ein echtes Beispiel. Bei jedem Schritt werde ich den Meeting-Planer-Code als Open-Source-Beispiele veröffentlichen, von denen Sie lernen können. Ich werde auch auf geschäftliche Probleme im Zusammenhang mit dem Start eingehen, sobald sie auftreten.
Warum eine API für Ihr Startup erstellen?
Der Hauptgrund, warum ich zu diesem Zeitpunkt eine API zu Meeting Planner hinzufüge, ist die Erstellung einer Grundlage für die Erstellung einer mobilen iOS-Anwendung. Die mobile App verwendet die API, um Benutzer zu registrieren und anzumelden und ihnen dann zu ermöglichen, Besprechungen zu planen.
APIs haben auch den sekundären Effekt, dass Sie den gesamten Code, den Sie bisher geschrieben haben, überdenken und besser organisieren können. Es gibt sicherlich Orte im Meeting-Planer-Code, die sich gewunden haben. Jetzt muss ich sie für mobile Apps noch einmal vereinfachen, um die Features über dem Kampf zu replizieren.
In Zukunft könnte es andere Gründe für den Aufbau der API geben. Ich möchte zum Beispiel Entwicklern von Drittanbietern ermöglichen, die Art von Besprechungen und Ereignissen zu erweitern, die von Meeting Planner geplant werden. Dadurch können sie zusätzliche Daten sammeln und freigeben.
Zur Erinnerung: Der gesamte Code für Meeting Planner wird als Open Source bereitgestellt und im Yii2 Framework for PHP geschrieben. Ein wesentlicher Teil dieser Episode beschreibt, wie das Yii Framework zur Unterstützung einer API verwendet wird. Wenn Sie mehr über Yii2 erfahren möchten, schauen Sie sich meine Parallel-Serie Programmierung mit Yii2 an.
Bevor ich auf den API-Code eingehe, möchte ich Sie ermutigen, Ihr erstes Meeting zu planen, damit Sie wissen, wovon ich rede.
Wenn Sie Fragen zu diesem Tutorial oder der Anwendung selbst haben, nehme ich an den folgenden Diskussionen teil und Sie können mich auch @lookahead_io auf Twitter erreichen. Ich bin immer offen für neue Feature-Ideen für Meeting Planner sowie Vorschläge für zukünftige Serienepisoden.
Entwerfen Ihrer API
Als ich mich darauf vorbereitete, die API zu erstellen, gab es verschiedene Konzepte, die ich verstehen musste. Ich habe einige davon in Programmieren mit Yii2 angesprochen: Aufbau einer RESTful API (Envato Tuts +).
Zuerst musste ich einen Endpunkt für die API erstellen, in dem alle Anrufe von mobilen Apps eingehen würden. Ich entschied mich, einen unabhängigen dritten Baum in dem Yii Advanced Application Framework, z. https://api.meetingplanner.io statt https://meetingplanner.io/api/. Dies ermöglicht eine saubere Trennung des API-Codes vom Rest des Dienstes.
Zweitens musste ich Sicherheit in der API entwerfen. Im heutigen Tutorial werde ich die einfache Alpha-Sicherheit demonstrieren, die wir verwenden, aber wir werden sie im Laufe der Zeit verstärken, und ich werde wahrscheinlich in Zukunft mehr darüber schreiben. Es gibt einen Sicherheitsaspekt bei der Verwendung und Übertragung der API-Schlüssel und -Anfragen, aber es ist auch wichtig sicherzustellen, dass die API die Sicherheitsprotokolle der Anwendung erzwingt. Beispielsweise kann ein Benutzer die Teilnehmer eines Meetings nicht anfordern, wenn sie nicht der Organisator des Meetings oder einer seiner Teilnehmer sind.
Drittens möchte ich den API-Code für die Versionierung vorbereiten. Zum Beispiel könnte eine ältere iOS-Anwendung, die nicht aktualisiert wurde, API v1.0 verwenden, während ein späteres Update API v2.0 aufrufen kann. Yii stellt Methoden dafür zur Verfügung, aber ich habe sie noch nicht im aktuellen Design implementiert.
Viertens wollte ich so weit wie möglich den REST-Standards entsprechen. Das ist etwas, was ich begonnen habe, aber es wird mehr Forschung erfordern, um es vollständig zu implementieren.
Zu guter Letzt musste ich zunächst die Breite der Funktionalität ansprechen, die die API bereitstellen würde. Zunächst konzentrierte ich mich bei der Entwicklung mobiler Anwendungen auf das Erstellen schreibgeschützter Funktionen. Das heutige Tutorial und der Code konzentrieren sich hauptsächlich auf Funktionen für schreibgeschützte Anwendungen, d. H. Zeigen Sie mir die Meetings des Benutzers. Aber es beinhaltet auch die Benutzerregistrierung. In naher Zukunft werden wir weitere Schreibfunktionen hinzufügen, z. B. ein Meeting erstellen, einen Teilnehmer hinzufügen, einen Meeting-Ort hinzufügen, eine Einladung abschließen usw.
Betrachten Sie dieses Tutorial als ersten Schritt zu einer robusten, abgeschlossenen Service-API für unsere Anwendung.
Erstellen der API
Erstellen der API-Servicebaumstruktur

Meeting Planner verwendet das Yii Advanced Application-Framework, das eine Front-End-Struktur für die Anwendung und eine Back-End-Struktur für die Verwaltungskomponente enthält, und wir erstellen eine dritte Struktur für die API.
Ich habe beschrieben, wie Sie dies früher in Programmieren mit Yii2: Erstellen einer RESTful API (Envato Tuts +) tun.
Zuerst habe ich den Back-End-Baum und die zugehörigen Umgebungseinstellungen dupliziert:
$ cp -R backend api $ cp -R environments/dev/backend/ environments/dev/api $ cp -R environments/prod/backend/ environments/prod/api
Und ich habe die @api alias to /common/config/bootstrap.php hinzugefügt:
<?php Yii::setAlias('@common', dirname(__DIR__)); Yii::setAlias('@frontend', dirname(dirname(__DIR__)) . '/frontend'); Yii::setAlias('@backend', dirname(dirname(__DIR__)) . '/backend'); Yii::setAlias('@api', dirname(dirname(__DIR__)) . '/api'); Yii::setAlias('@console', dirname(dirname(__DIR__)) . '/console');
Als nächstes beginnen wir mit dem Aufbau der Kernfunktionalität.
Sicherung der API
Beim Erstellen und Testen der iOS-Anwendung habe ich einige grundlegende Sicherheitsfunktionen entwickelt. Ich werde das in Zukunft robuster machen.
Alle API-Aufrufe müssen eine app_id
und app_secret
kennen. Diese werden in irgendeiner Form von HTTPS übertragen. Es gibt jedoch keine Garantie dafür, dass wir diese schützen können. Daher müssen wir die Anwendung so konzipieren, dass sie gegen die Entdeckung dieser Schlüssel resistent ist.
Im Moment habe ich die Datei mp.ini in / var / secure so erweitert, dass sie Folgendes enthält:
... sentry_key_public = "xxxxxxxx" sentry_key_private = "xxxxxx" sentry_id ="nnnnnn" app_id = "xnxnxnxxnxnxn" app_secret ="xnxnxnxnxnxnxnxnxnxnxnxnxnxnxnxnxn"
Dann habe ich ein Service.php-Modell erstellt, um die Verifizierung dieser Schlüssel zu verwalten. Da wir dies robuster machen, muss ich nur einen Code ändern.
class Service extends Model { public static function verifyAccess($app_id,$app_secret) { if ($app_id == Yii::$app->params['app_id'] && $app_secret == Yii::$app->params['app_secret']) { Yii::$app->params['site']['id']=SiteHelper::SITE_SP; return true; } else { return false; } }
Als Nächstes habe ich in allen API-Controllern eine beforeAction
eingerichtet, um die obige Methode wiederzuverwenden:
public function beforeAction($action) { // your custom code here, if you want the code to run before action filters, // which are triggered on the [[EVENT_BEFORE_ACTION]] event, e.g. PageCache or AccessControl if (!parent::beforeAction($action)) { return false; } if (Service::verifyAccess(Yii::$app->getRequest()->getQueryParam('app_id'),Yii::$app->getRequest()->getQueryParam('app_secret'))) { return true; } else { echo 'your api keys are from the dark side'; Yii::$app->end(); } }
Die Hauptschwächen hierbei sind, dass die Sicherheitsschlüssel bei jedem Aufruf übertragen werden und die Abfrageparameter nicht signiert sind. Die Übertragung über HTTPS hilft, ist aber nicht vollständig sicher. Ich werde das in Zukunft verbessern.
Registrierung und Login
Die einzigen zwei API-Aufrufe, die vollständig auf API-Schlüsseln basieren, sind die Registrierung und die Anmeldung. Mobile Nutzer können sich über OAuth registrieren und uns ihre OAuth-Service-Token senden, oder sie können ihre E-Mail-Adresse direkt an uns übermitteln.
Nach dem Empfang erhält jeder Benutzer ein eindeutiges Token, und dieses Token sichert die zukünftigen API-Aufrufe dieses Benutzers.
Ich muss noch mehr tun, um die Sicherheit zu verbessern, aber das werde ich auch heute nicht behandeln.
Hier ist der erste Code, um einen Benutzer über die API zu registrieren und ein Token zu erstellen:
public static function signupUser($email, $firstname='',$lastname='') { $username = $fullname = $firstname.' '.$lastname; if ($username == ' ') $username ='ios'; if (isset($username) && User::find()->where(['username' => $username])->exists()) { $username = User::generateUniqueUsername($username,'ios'); } $password = Yii::$app->security->generateRandomString(12); $user = new User([ 'username' => $username, // $attributes['login'], 'email' => $email, 'password' => $password, 'status' => User::STATUS_ACTIVE, ]); $user->generateAuthKey(); $user->generatePasswordResetToken(); $transaction = $user->getDb()->beginTransaction(); if ($user->save()) { $ut = new UserToken([ 'user_id' => $user->id, 'token' => Yii::$app->security->generateRandomString(40), ]); if ($ut->save()) { User::completeInitialize($user->id); UserProfile::applySocialNames($user->id,$firstname,$lastname,$fullname); $transaction->commit(); return $user->id; } else { print_r($auth->getErrors()); } } else { $transaction->rollBack(); print_r($user->getErrors()); } }
Das UserToken ist eine einzigartige 40-stellige zufällige Zeichenfolge, die es noch schwieriger zu erraten, als glauben, dass Amerika Donald Trump wählen würde, um sie zu führen.
$ut = new UserToken([ 'user_id' => $user->id, 'token' => Yii::$app->security->generateRandomString(40), ]);
Der Besprechungscontroller
Betrachten wir nun Aufrufe für einen bestimmten Bereich der API, in denen Informationen zu Meetings angefordert werden. Hier ist der erste Teil von /api/controllers/MeetingController.php:
<?php namespace api\controllers; use Yii; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; use yii\web\Response; use api\models\MeetingAPI; use api\models\Service; class MeetingController extends Controller { public function behaviors() { return [ 'verbs' => [ 'class' => VerbFilter::className(), 'actions' => [ 'delete' => ['POST'], ], ], ]; } public function beforeAction($action) { // your custom code here, if you want the code to run before action filters, // which are triggered on the [[EVENT_BEFORE_ACTION]] event, e.g. PageCache or AccessControl if (!parent::beforeAction($action)) { return false; } if (Service::verifyAccess(Yii::$app->getRequest()->getQueryParam('app_id'),Yii::$app->getRequest()->getQueryParam('app_secret'))) { return true; } else { echo 'your api keys are from the dark side'; Yii::$app->end(); } }
Beachten Sie oben, dass jede Aktion überprüft, ob die Token korrekt sind.
Dann ist jeder API-Aufruf für Meetings identisch strukturiert, wie unten gezeigt (lobe meinen Versuch der Disziplin):
public function actionList($app_id='', $app_secret='',$token='',$status=0) { Yii::$app->response->format = Response::FORMAT_JSON; return MeetingAPI::meetinglist($token,$status); } public function actionHistory($app_id='', $app_secret='',$token='',$meeting_id=0) { Yii::$app->response->format = Response::FORMAT_JSON; return MeetingAPI::history($token,$meeting_id); } public function actionMeetingplaces($app_id='', $app_secret='',$token='',$meeting_id=0) { Yii::$app->response->format = Response::FORMAT_JSON; return MeetingAPI::meetingplaces($token,$meeting_id); } public function actionMeetingtimes($app_id='', $app_secret='',$token='',$meeting_id=0) { Yii::$app->response->format = Response::FORMAT_JSON; return MeetingAPI::meetingtimes($token,$meeting_id); } public function actionMeetingplacechoices($app_id='', $app_secret='',$token='',$meeting_place_id=0) { Yii::$app->response->format = Response::FORMAT_JSON; return MeetingAPI::meetingplacechoices($token,$meeting_place_id); } public function actionMeetingtimechoices($app_id='', $app_secret='',$token='',$meeting_time_id=0) { Yii::$app->response->format = Response::FORMAT_JSON; return MeetingAPI::meetingtimechoices($token,$meeting_time_id); } public function actionNotes($app_id='', $app_secret='',$token='',$meeting_id=0) { Yii::$app->response->format = Response::FORMAT_JSON; return MeetingAPI::notes($token,$meeting_id); } public function actionSettings($app_id='', $app_secret='',$token='',$meeting_id=0) { Yii::$app->response->format = Response::FORMAT_JSON; return MeetingAPI::settings($token,$meeting_id); } public function actionCaption($app_id='', $app_secret='',$token='',$meeting_id=0) { Yii::$app->response->format = Response::FORMAT_JSON; return MeetingAPI::caption($token,$meeting_id); } public function actionDetails($app_id='', $app_secret='',$token='',$meeting_id=0) { Yii::$app->response->format = Response::FORMAT_JSON; return MeetingAPI::details($token,$meeting_id); } public function actionReminders($app_id='', $app_secret='',$token='') { Yii::$app->response->format = Response::FORMAT_JSON; return MeetingAPI::reminders($token); }
Vorläufig umfasst jeder Aufruf die $app_id
, den $app_secret
und den $token
für den angemeldeten Benutzer. Ich werde dies für die Sicherheit in naher Zukunft ändern. Es ist sicher, aber nicht robust sicher.
Sehen wir uns actionList
an, in der der Filter für die Besprechung der Benutzer nach dem Argument $status
aufgelistet wird, um sie zu filtern:
public function actionList($app_id='', $app_secret='',$token='',$status=0) { Yii::$app->response->format = Response::FORMAT_JSON; return MeetingAPI::meetinglist($token,$status); }
Eventuell begrenzt die API die Anzahl der Besprechungsanfragen für jeden Status, d. H. Zeigt mir die letzten 15 Besprechungen im Planungsmodus durch diesen Benutzer an.
Alle Meeting-Methoden sind in das MeetingAPI-Modell integriert. Hier ist der Code für die Methode meetinglist()
:
<?php namespace api\models; use Yii; use yii\base\Model; use common\models\User; use common\components\MiscHelpers; use api\models\UserToken; use frontend\models\Meeting; use frontend\models\MeetingLog; use frontend\models\MeetingPlace; use frontend\models\MeetingTime; use frontend\models\MeetingReminder; use frontend\models\MeetingSetting; use frontend\models\MeetingNote; class MeetingAPI extends Model { public static function meetinglist($token,$status) { $user_id = UserToken::lookup($token); if (!$user_id) { return Service::fail('invalid token'); } if ($status == Meeting::STATUS_PLANNING || $status == Meeting::STATUS_SENT) { $queryStatus =[Meeting::STATUS_PLANNING,Meeting::STATUS_SENT]; } else { $queryStatus = $status; } // get calling user's timezone $timezone = MiscHelpers::fetchUserTimezone($user_id); $meeting_list = Meeting::find() ->joinWith('participants') ->where(['owner_id'=>$user_id]) ->orWhere(['participant_id'=>$user_id]) ->andWhere(['meeting.status'=>$queryStatus]) ->distinct() ->orderBy(['created_at'=>SORT_DESC]) ->all(); $meetings=[]; foreach ($meeting_list as $m) { $x = new \stdClass(); $x->id = $m->id; $x->owner_id= $m->owner_id; $x->meeting_type = $m->meeting_type ; $x->subject = $m->subject ; $x->message = $m->message ; $x->identifier = $m->identifier ; $x->status = $m->status ; $x->created_at = $m->created_at ; $x->logged_at = $m->logged_at ; $x->sequence_id = $m->sequence_id ; $x->cleared_at = $m->cleared_at; $x->site_id = $m->site_id ; if ($status >= Meeting::STATUS_CONFIRMED) { $x->chosenTime=Meeting::getChosenTime($m->id); $x->caption = $m->friendlyDateFromTimestamp($x->chosenTime->start,$timezone,true,true).' '.$m->getMeetingParticipants(); $x->chosenPlace = Meeting::getChosenPlace($m->id); if ($x->chosenPlace!==false) { $x->place = $x->chosenPlace->place; $x->gps = $x->chosenPlace->place->getLocation($x->chosenPlace->place->id); $x->noPlace = false; } else { $x->place = false; $x->noPlace = true; $x->gps = false; } } else { $x->chosenTime=0; $x->chosenPlace = 0; $x->caption = $m->getMeetingParticipants(); } $meetings[]=$x; unset($x); } return $meetings; }
Zuerst verifiziert die Methode das Token als zu dem Benutzer gehörend:
$user_id = UserToken::lookup($token); if (!$user_id) { return Service::fail('invalid token'); }
Hier ist der Code für UserToken::lookup()
:
public static function lookup($token) { // lookup token for user_id $ut = UserToken::find() ->where(['token'=>$token]) ->one(); if (!is_null($ut)) { return $ut->user_id; } else { return false; } }
Dann überprüfen wir den Filter auf $status
und holen die $timezone
des Benutzers:
if ($status == Meeting::STATUS_PLANNING || $status == Meeting::STATUS_SENT) { $queryStatus =[Meeting::STATUS_PLANNING,Meeting::STATUS_SENT]; } else { $queryStatus = $status; } // get calling user's timezone $timezone = MiscHelpers::fetchUserTimezone($user_id);
Und schließlich fragen wir eine Liste der Meetings des Benutzers ab und transponieren sie manuell in ein Array von Objekten:
$meeting_list = Meeting::find() ->joinWith('participants') ->where(['owner_id'=>$user_id]) ->orWhere(['participant_id'=>$user_id]) ->andWhere(['meeting.status'=>$queryStatus]) ->distinct() ->orderBy(['created_at'=>SORT_DESC]) ->all(); $meetings=[]; foreach ($meeting_list as $m) { $x = new \stdClass(); $x->id = $m->id; $x->owner_id= $m->owner_id; $x->meeting_type = $m->meeting_type ; $x->subject = $m->subject ; $x->message = $m->message ; $x->identifier = $m->identifier ; $x->status = $m->status ; $x->created_at = $m->created_at ; $x->logged_at = $m->logged_at ; $x->sequence_id = $m->sequence_id ; $x->cleared_at = $m->cleared_at; $x->site_id = $m->site_id ; if ($status >= Meeting::STATUS_CONFIRMED) { $x->chosenTime=Meeting::getChosenTime($m->id); $x->caption = $m->friendlyDateFromTimestamp($x->chosenTime->start,$timezone,true,true).' '.$m->getMeetingParticipants(); $x->chosenPlace = Meeting::getChosenPlace($m->id); if ($x->chosenPlace!==false) { $x->place = $x->chosenPlace->place; $x->gps = $x->chosenPlace->place->getLocation($x->chosenPlace->place->id); $x->noPlace = false; } else { $x->place = false; $x->noPlace = true; $x->gps = false; } } else { $x->chosenTime=0; $x->chosenPlace = 0; $x->caption = $m->getMeetingParticipants(); } $meetings[]=$x; unset($x); } return $meetings;
Während es möglicherweise eine einfachere Möglichkeit gibt, die Datenbankergebnisse zuzuordnen, die in der API zurückgegeben werden, ermöglicht es die manuelle Umsetzung der komplexesten Tabelle Meeting, zu steuern, was die API-Ergebnisse für Programmierer bereitstellen. Es ist tatsächlich eine Chance für mich, die API gegenüber den ursprünglichen Code- und Datenbankeigenschaften zu verbessern und zu vereinfachen.
Beispielsweise gibt es Code, mit dem Meeting Planner Unterüberschriften in der Benutzeroberfläche generieren muss, die nicht in der Datenbank gespeichert sind. Anstatt zu verlangen, dass die iOS-Anwendung diesen komplexen Code dupliziert, generieren wir nur die Unterüberschrift und geben sie in den API-Ergebnissen zurück.
API-Aufrufe durchführen
Hier ist eine vorläufige Möglichkeit, API-Aufrufe zu erstellen und zu testen. Zum Beispiel, wenn ich den folgenden URL-Aufruf mache:
http://apix.meetingplanner.io/meeting/list/?app_id=xxx&app_secret=xxxxx&token=yyyy
Das wird funktionieren. Aber zum Testen und um es in Aktion zu sehen, habe ich Postman verwendet, eine Chrome-App-Erweiterung, die sehr nützlich ist.
So können Sie einen API-Aufruf mit dem Postman UX erstellen:

Und so sehen die Ergebnisse aus:

Das ist nur eine einfache Möglichkeit, das rohe Ergebnis meines Entwicklungsservers zu sehen, auf dem alle meine Meetings angezeigt werden:
[ { "id": 207, "owner_id": 1, "meeting_type": 0, "subject": "New Mtg to Test", "message": "", "identifier": "dAefqLGi", "status": 20, "created_at": 1475285105, "logged_at": 1476642100, "sequence_id": "0", "cleared_at": 1475780470, "site_id": 0, "chosenTime": 0, "chosenPlace": 0, "caption": "with Jeff Reifman and b1@lookahead.me" }, { "id": 206, "owner_id": 1, "meeting_type": 150, "subject": "Ignore - just testing", "message": "", "identifier": "ITJpSmlo", "status": 20, "created_at": 1474706654, "logged_at": 1474706702, "sequence_id": "0", "cleared_at": 1474706732, "site_id": 0, "chosenTime": 0, "chosenPlace": 0, "caption": "with Jeff Reifman and jimmyblack@gmail.com" }, { "id": 205, "owner_id": 1, "meeting_type": 110, "subject": "Our Upcoming Meeting Test", "message": "", "identifier": "vkVPWVmH", "status": 20, "created_at": 1474677013, "logged_at": 1474921968, "sequence_id": "0", "cleared_at": 1474920744, "site_id": 0, "chosenTime": 0, "chosenPlace": 0, "caption": "with Jeff Reifman and tanyaryan@lookahead.me" }, ...
Das war es fürs Erste. Sie können die Version in der API-Struktur durchsuchen und viele andere Methoden anzeigen. Wenn ich die Sicherheit verbessere und die Funktionalität der API verbessere, werde ich versuchen, mehr darüber zu schreiben.
Vorausschauen
Ich hoffe, dir hat das heutige Tutorial gefallen. Offensichtlich wird die API wachsen und sich ändern, wenn unsere mobile Entwicklung voranschreitet. Wie ich bereits sagte, werde ich die Sicherheit erhöhen und die Funktionalität erweitern.
Wenn Sie es noch nicht getan haben, planen Sie jetzt Ihr erstes Meeting mit Meeting Planner!
Sie können mich auch erreichen @lookahead_io. Ich bin immer offen für neue Feature-Ideen und Themenvorschläge für zukünftige Tutorials. Oder probieren Sie unseren Helpdesk aus und öffnen Sie einen Fehlerbericht oder ein Feature-Anfrage-Ticket.
Bleiben Sie dran für all diese und weitere anstehende Tutorials, indem Sie die Building Your Startup With PHP-Serie ausprobieren.
verwandte Links
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post