Acest tutorial face parte din Construirea sistemului de pornire cu seria PHP pe Envato Tuts +. În această serie, vă conduc prin lansarea unui startup de la concept la realitate, utilizând ajutorul meu Planificatorul întâlnirilor aplicație ca exemplu de viață reală. Fiecare pas de-a lungul drumului, voi lansa codul Planificatorului de Întâlniri ca exemple de sursă deschisă pe care le puteți învăța. Voi aborda, de asemenea, problemele de afaceri legate de pornire în momentul în care apar.
Planificatorul de întâlniri se apropie de lansarea alpha, avem nevoie de o modalitate de a răspunde solicitărilor de asistență cu utilizatorii și de a monitoriza activitatea. Cu alte cuvinte, trebuie să construim un tablou de bord administrativ cu gestionarea și raportarea utilizatorilor. În discuțiile cu un consilier, am discutat că, pe măsură ce mă adresez potențialilor investitori, va trebui să am date excelente care detaliază comportamentul utilizatorilor și creșterea serviciilor.
În episodul de astăzi vom construi fundația pentru tabloul nostru administrativ, vom prelua și vom crea o parte din rapoartele inițiale live și istorice. De exemplu, vom ști câte persoane s-au înregistrat în orice moment, câte întâlniri au fost programate și ce procent din participanții invitați le-a plăcut serviciului pentru a-și organiza propriile întâlniri. De fapt, a fost destul de distractiv să construim aceste lucruri și să vedem datele, chiar dacă suntem pre-lansați.
Dacă nu ați încercat încă Planificatorul de întâlniri (și doriți să vă prezentați personal în datele agregate), continuați și programați prima dvs. întâlnire. Particip la comentariile de mai jos, deci spune-mi ce crezi! Puteți să mă contactați și pe Twitter @reifman. Sunt interesat în special dacă doriți să sugerați noi caracteristici sau subiecte pentru tutoriale viitoare.
Ca un memento, întregul cod pentru Planificatorul întâlnirilor este scris în cadrul Yii2 Framework for PHP. Dacă doriți să aflați mai multe despre Yii2, consultați seria noastră paralelă Programming With Yii2.
Yii2 oferă site-uri din față și din spate în cadrul aplicației avansate de configurare a aplicațiilor. Puteți citi mai multe despre el în tutorialul meu Envato Tuts +, Cum se programează cu Yii2: Utilizarea șablonului aplicației avansate. În esență, site-ul front-end al șablonului avansat oferă funcționalități orientate spre utilizatori, iar site-ul de back-end este realizat pentru tabloul de bord al unui serviciu și site-ul de administrare.
Pentru a-l activa, am avut nevoie doar de instalarea de site-uri Apache în mediul local MAMP și pe serverul de producție Ubuntu. De exemplu, aici este configurația Apache de pe serverul de producție pentru a încărca / Backend / web
site-ul:
Numele serverului dvs.-administration-site.com DocumentRoot "/ var / www / mp / backend / web" # Utilizați mod_rewrite pentru sprijinirea URL-ului destul de mare RewriteEngine on # Dacă există un director sau un fișier, utilizați cererea direct RewriteCond% REQUEST_FILENAME! -f RewriteCond% REQUEST_FILENAME! -d # În caz contrar, trimiteți cererea la index.php RewriteRule. index.php SSLCertificateFile /etc/letsencrypt/live/meetingplanner.io/cert.pem SSLCertificateKeyFile /etc/letsencrypt/live/meetingplanner.io/privkey.pem Include /etc/letsencrypt/options-ssl-apache.conf SSLCertificateChainFile / etc / letsencrypt / live /meetingplanner.io/chain.pem
Apoi, am construit un nou aspect pentru site-ul back-end bazat pe site-ul front-end, dar cu diferite opțiuni de meniu. Am decis că pagina de start va fi redirecționată către o pagină a statisticilor în timp real. Și meniurile ar oferi legături către date în timp real, date de ieri la miezul nopții și date istorice. Voi explica un pic mai mult din acest lucru pe măsură ce vom continua.
Iată \ backend \ views \ layouts \ main.php cu meniul:
beginBody ()?>Ypi :: t ('backend', 'Planner de întâlniri'), 'brandUrl' => 'https://meetingplanner.io', 'opțiuni' => 'navbar-inverse navbar-top-top ',],]); $ menuItems [] = ['label' => 'Timp Real', 'items' => [['label' => Yii :: t (' date / curente]],]]; $ menuItems [] = ['label' => 'Ieri', 'items' => [['label' => Yii :: t (' datele utilizatorului']], ] ]; $ menuItems [] = ['label' => 'Istoric', 'elemente' => [['label' => Yii :: t -date']], ], ]; dacă Yii :: $ app-> user-> isGuest) $ menuItems [] = ['label' => 'Login', 'url' => ['/ site / login']]; altceva $ menuItems [] = ['label' => 'Account', 'items' => [['label' => 'Logout'. '),' url '=> [' / site / logout '],' linkOptions '=> [' metode de date '=>' post '],],];]; echo Nav :: widget (['options' => ['class' => 'navbar-nav navbar-right'], 'items' => $ menuItems,]); NavBar :: end (); ?>= Breadcrumbs::widget([ 'links' => isset ($ this-> params ['breadcrumbs'])? $ this-> params ['breadcrumbs']: [],])?> = $content ?>
Pentru raportarea statistică inițială, m-am concentrat pe date simple în timp real și date istorice detaliate. De exemplu, datele în timp real vă vor indica numărul de utilizatori și întâlnirile construite pe sistem până în prezent și starea acestora.
Datele istorice vă vor indica numărul de utilizatori și întâlniri realizate în timp, precum și alte date interesante - în special curbele de creștere pe care eu și potențialii investitori le-ar putea interesa.
Pagina cu date în timp real trebuie să arate o imagine instantanee a ceea ce se întâmplă pe site. Inițial, am vrut să știu:
Pentru a realiza acest lucru, am creat un model back-end DataController.php și Data.php. De asemenea, am făcut un pas înainte și, mai degrabă decât să creez un cod HTML brut, pentru a afișa acest lucru, am creat ActiveDataProviders din interogările mele și le-am hrănit în widgeturile rețelei Yii; rezultatul pare mai bine și este mai simplu de construit și menținut.
Acest cod interoghează numărul de întâlniri din sistem grupate după statutul lor:
funcția statică publică getRealTimeData () $ data = new \ stdClass (); $ date-> meetings = new ActiveDataProvider (['interogare' => întâlnire :: find () -> selectați ('status', COUNT (*) AS dataCount ')) -> groupBy (['status']), 'paginare' => ['pageSize' => 20,],]);
Acest cod din /backend/views/data/current.php afișează:
title = Yii :: t ("backend", "Planificator de Întâlniri"); ?>Date în timp real
Întâlniri
= GridView::widget([ 'dataProvider' => $ data-> meetings, 'columns' => [['label' => 'Status', 'attribute' => 'status' întoarcere ''.Meeting :: lookupStatus ($ Model-> stare).'„; ,], "dataCount",],]); ?>Arată așa (datele sunt mici, deoarece site-ul nu a lansat încă!):
Apoi, am creat mai multe interogări în timp real, iar restul paginii arată astfel:
În ceea ce privește Oameni activi și Prin invitație coloanele de mai sus, dacă invitați o persoană la o întâlnire, le numărăm ca utilizator prin invitație până când își creează o parolă sau leagă contul social. Până atunci, accesul numai la Planificatorul de întâlniri se face prin intermediul link-ului dvs. de invitație prin e-mail și a codului său de autentificare.
Evident, voi extinde opțiunile de raportare în timp real pe măsură ce proiectul evoluează.
Raportarea datelor istorice
Generarea rapoartelor istorice pentru activitățile sistemice s-a dovedit un pic mai importantă. Am decis să creez câteva straturi dependente de colectare de date.
Nivelul inferior este un tabel UserData care rezumă starea activității istorice a unei persoane până la o anumită zi la miezul nopții. În esență, vom face asta noaptea.
Stratul superior este tabelul HistoricalData care își construiește calculele folosind tabelul UserData din noaptea precedentă.
De asemenea, trebuia să scriu codul care a construit cele două tabele de la zero, deoarece serviciul nostru a fost puțin activ timp de câteva luni.
Te voi ghida prin modul în care am făcut asta. Rezultatul sa dovedit destul de bine.
Crearea migrărilor de tabel
Aici este migrarea tabelului pentru UserData - conține datele pe care am vrut să le calc pe timp de noapte pentru a ajuta calculele istorice:
funcția publică sus () $ tableOptions = null; dacă ($ this-> db-> driverName === 'mysql') $ tableOptions = 'SETUL CHARACTERU utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB'; $ this-> createTable ('% user_data', ['id' => Schema :: TYPE_PK, 'user_id' => Schema :: TYPE_BIGINT. TYPE_SMALLINT 'NOT NULL', 'invite_then_own' => Schema :: TYPE_SMALLINT. 'NOT NULL', 'count_meetings' => Schema :: TYPE_INTEGER. 'NOT NULL', 'count_meetings_last30' => Schema :: TYPE_INTEGER. ',' count_meeting_participant '=> Schema :: TYPE_INTEGER.NOT NULL', 'count_meeting_participant_last30' => Schema :: TYPE_INTEGER. 'NOT NULL', 'count_places' => Schema :: TYPE_INTEGER. 'NOT NULL', 'created_at' => Schema :: TYPE_INTEGER. 'NOT NULL', 'updated_at' => Schema :: TYPE_INTEGER. $ this-> addForeignKey ('fk_user_data_user_id', '% user_data', 'user_id', '% user', 'id', 'CASCADE', 'CASCADE');De exemplu,
count_meeting_participant_last30
este numărul de întâlniri la care a fost invitată această persoană în ultimele 30 de zile.Aici este migrarea tabelului pentru
Date istorice
-aproape toate coloanele din acest tabel trebuie calculate din diferite straturi de date:funcția publică sus () $ tableOptions = null; dacă ($ this-> db-> driverName === 'mysql') $ tableOptions = 'SETUL CHARACTERU utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB'; $ this-> createTable ('% historical_data', ['id' => Schema :: TYPE_PK, 'date' => Schema :: TYPE_INTEGER. TYPE_FLOAT "NU NULL", "percent_own_meeting_last30" => Schema :: TYPE_FLOAT.NOT NULL, //% dintre utilizatorii invitați de alții care dețin o întâlnire 'percent_invited_own_meeting' => Schema :: TYPE_FLOAT.NOT NULL, % '=> Schema :: TYPE_FLOAT.' NOT NULL ',' percent_participant_last30 '=> Schema :: TYPE_FLOAT.NOT NULL', 'count_users' => Schema :: TYPE_INTEGER.NOT NULL ',' count_meetings_completed '=> :: NOTA NULL ',' count_meetings_planning '=> Schema :: TYPE_INTEGER.' NOT NULL ',' count_places '=> Schema :: TYPE_INTEGER' NOT NULL ',' average_meetings '=> Schema :: TYPE_FLOAT. NOT NULL ',' average_friends '=> Schema :: TYPE_FLOAT.' NOT NULL ',' average_places '=> Schema :: TYPE_FLOAT. source_facebook '=> Schema :: TYPE_INTEGER.' NOT NULL ',' source_linkedin '=> Schema :: TY PE_INTEGER. "NU NULL",], $ tableOptions);În discuția cu consilierul meu am realizat că potențialii investitori vor dori să știe cum răspund oamenii la site. Am creat o măsurătoare pentru o metrică numită
percent_invited_own_meeting
, scurt pentru procentul de utilizatori invitați la prima lor întâlnire care i-au plăcut suficient serviciului pentru a-l utiliza pentru a-și programa întâlnirea în viitor. Voi examina mai multe despre calcule un pic mai jos.Toate migrațiile se află în / console / migrații. Iată cum arată atunci când rulați migrarea bazei de date.
$ .yy migrate / up Yii Migration Tool (bazat pe Yii v2.0.8) Total 2 noi migrări care trebuie aplicate: m160609_045838_create_user_data_table m160609_051532_create_historical_data_table Aplicați migrațiile de mai sus? (da: nu): da *** aplicând m160609_045838_create_user_data_table> creați tabelul % user_data ... făcut (timpul: 0,003s)> adăugați cheia străină fk_user_data_user_id: user_data (timp: 0.004s) *** aplicat m160609_045838_create_user_data_table (time: 0.013s) *** aplicarea m160609_051532_create_historical_data_table> crea tabelul % historical_data ... făcut (timpul: 0.003s) ** * aplicat m160609_051532_create_historical_data_table (time: 0.005s) au fost aplicate 2 migrații. Sa migrat cu succes.Colectarea datelor de raportare
În fiecare noapte după miezul nopții, o sarcină de fundal va calcula statisticile din noaptea precedentă. Iată metoda de fundal:
funcția publică funcțiaOvernight () $ since = mktime (0, 0, 0); $ după = mktime (0, 0, 0, 2, 15, 2016); UserData :: Calculeaza (false, $ după); HistoricalData :: Calculeaza (false, $ după);Am creat un job cron pentru a fugi
actionOvernight
la ora 1:15 pe zi. Notă: Când vă concentrați programarea intenționată într-o zi și o noapte de pornire, o activitate cron este despre toată acțiuneaOvernight veți obține.Pentru a construi istoria trecutului, am creat o singură dată
recalc ()
funcţie. Acest lucru umple mesele și construiește fiecare masă ca și cum s-ar întâmpla o zi la un moment dat.funcția statică publică recalc () UserData :: reset (); HistoricalData :: reset (); $ după = mktime (0, 0, 0, 2, 15, 2016); $ din = mktime (0, 0, 0, 4, 1, 2016); în timp ce ($ din < time()) UserData::calculate($since,$after); HistoricalData::calculate($since,$after); // increment a day $since+=24*60*60;Notă:
după
timpul este o soluție pentru a exclude pe unii dintre primii utilizatori care s-au înscris înainte de a putea programa o întâlnire. Am dorit ca datele istorice să reflecte o imagine mai precisă a activității recente (în prezent există câteva sute de conturi mai vechi fără activitate). Probabil că o voi elimina mai târziu.Calcularea tabelului de date utilizator
Iată codul care conține populația
Datele utilizatorului
masa de noapte:funcția statică publică calcul ($ since = false, $ after = 0) if ($ since === false) $ since = mktime (0, 0, 0); $ monthago = $ de la- (60 * 60 * 24 * 30); $ all = Utilizator :: find () -> unde ('created_at>'. $ after) -> andWhere ('created_at<'.$since)->toate(); foreach ($ toate ca $ u) // a crea o nouă înregistrare pentru utilizator sau a actualiza vechea $ ud = UserData :: find () -> unde (['user_id' => $ u-> id]) - ); dacă (is_null ($ ud)) $ ud = noua UserData (); $ ud-> user_id = $ u-> id; $ Ud-> Salvare (); $ user_id = $ u-> id; // numără întâlnirile pe care le-au organizat $ ud-> count_meetings = întâlnire :: find () -> unde (['owner_id' => $ user_id]) -> andWhere ('created_at<'.$since)->numara(); $ ud-> count_meetings_last30 = Întâlnire :: find () -> unde (['owner_id' => $ user_id]) -> andWhere ('created_at<'.$since)->andWhere ( 'created_at> =' $ monthago.) -> count (); // numărați întâlnirile la care au fost invitați la $ ud-> count_meeting_participant = Participant :: find () -> unde (['participant_id' => $ user_id]) -> andWhere ('created_at<'.$since)->numara(); $ ud-> count_meeting_participant_last30 = Participant :: găsi () -> unde (['participant_id' => $ user_id]) -> andWhere ('created_at<'.$since)->andWhere ( 'created_at> =' $ monthago.) -> count (); // numărul de locuri și prieteni $ ud-> count_places = UserPlace :: find () -> unde (['user_id' => $ user_id]) -> andWhere ('created_at<'.$since)->numara(); $ ud-> count_friends = Friend :: find () -> unde (['user_id' => $ user_id]) -> andWhere ('created_at<'.$since)->numara(); // Calculați invitația mai întâi de la Participant propriu, apoi organizator $ first_invite = Participant :: găsi () -> unde (['participant_id' => $ user_id]) -> andWhere ('created_at<'.$since)->comanda ('created_at asc') -> one (); $ first_organized = Întâlnire :: find () -> unde (['owner_id' => $ user_id]) -> andWhere ('created_at<'.$since)->comanda ('created_at asc') -> one (); $ ud-> invite_then_own = 0; dacă (! is_null ($ first_invite) &&! is_null ($ first_organized)) if ($ first_invite-> created_at < $first_organized->created_at && $ first_organized-> created_at < $since) // they were invited as a participant earlier than they organized their own meeting $ud->invite_then_own = 1; dacă (Auth :: find () -> unde (['user_id' => $ user_id]) -> count ()> 0) $ ud-> is_social = 1; altceva $ ud-> is_social = 0; $ ud-> actualizare ();Este mai mult decât numărarea totalurilor pentru utilizatorii întâlnirilor, locurilor, prietenilor și, în unele cazuri, în intervalul de timp din ultimele 30 de zile.
Iată codul care detectează dacă utilizatorul a ales să programeze o întâlnire utilizând serviciul după ce a fost invitat:
$ ud-> invite_then_own = 0; dacă (! is_null ($ first_invite) &&! is_null ($ first_organized)) if ($ first_invite-> created_at < $first_organized->created_at && $ first_organized-> created_at < $since) // they were invited as a participant earlier than they organized their own meeting $ud->invite_then_own = 1;Calculând datele istorice
Iată codul care folosește
Datele utilizatorului
pentru a populaDate istorice
:funcția statică publică calcul ($ since = false, $ after = 0) if ($ since === false) $ since = mktime (0, 0, 0); // creați o nouă înregistrare pentru dată sau actualizați $ hd = HistoricalData :: find () -> unde (['date' => $ since]) -> one (); dacă (is_null ($ hd)) $ hd = date istorice noi (); $ hd-> date = $ since; $ action = 'salvați'; altceva $ action = 'actualizare'; // calcula $ count_meetings_completed $ hd-> count_meetings_completed = Întâlnire :: find () -> unde (['status' => Întâlnire :: STATUS_COMPLETED]) -> andWhere ('created_at<'.$since)->numara();; // calc $ $ count_meetings_planning $ hd-> count_meetings_planning = Întâlnire :: find () -> where ('status<'.Meeting::STATUS_COMPLETED)->andWhere ( "created_at<'.$since)->numara();; // calcula $ count_places $ hd-> count_places = Locul :: find () -> unde ('created_at>'. $ after) -> andWhere ('created_at<'.$since)->numara(); // calcula $ source_google $ hd-> source_google = Auth :: find () -> unde (['source' => 'google']) -> count (); // calcula $ source_facebook $ hd-> source_facebook = Auth :: find () -> unde (['source' => 'facebook']) -> count (); // calcula $ source_linkedin $ hd-> source_linkedin = Auth :: find () -> unde (['source' => 'linkedin']) -> count (); // total utilizatori $ total_users = UserData :: find () -> count (); // calcula $ count_users $ hd-> count_users = $ total_users; // utilizator :: găsi () -> în cazul în care ( 'starea <>' utilizator :: STATUS_DELETED.) -> andWhere ( 'created_at>' $ dupa.) -> count (); $ total_friends = Friend :: find () -> unde ('created_at>'. $ after) -> andWhere ('created_at<'.$since)->numara(); $ total_places = Locul :: find () -> unde ('created_at>'. $ after) -> andWhere ('created_at<'.$since)->numara(); dacă ($ total_users> 0) $ hd-> average_meetings = ($ hd-> count_meetings_completed + $ hd-> count_meetings_planning) / $ total_users; $ hd-> mediu_friends = $ total_friends / $ total_users; $ hd-> media_places = $ total_places / $ total_users; $ hd-> percent_own_meeting = UserData :: găsi () -> unde ('count_meetings> 0') -> count () / $ total_users; $ hd-> percent_own_meeting_last30 = UserData :: găsi () -> unde ('count_meetings_last30> 0') -> count () / $ total_users; $ hd-> percent_participant = UserData :: găsi () -> unde ('count_meeting_participant> 0') -> count () / $ total_users; $ hd-> percent_participant_last30 = UserData :: găsi () -> unde ('count_meeting_participant_last30> 0') -> count () / $ total_users; $ query = (nou \ yii \ db \ Query ()) -> din ('user_data'); $ sum = $ interogare-> suma ('invite_then_own'); $ HD-> percent_invited_own_meeting = $ TOTAL_USERS sum / $; dacă ($ action == 'salvați') $ hd-> save (); altceva $ hd-> update ();Sintetizează totalurile și calculează procentele și mediile.
Iată cum arată produsul finit:
Chiar dacă vedem analiza utilizării numai pre-alfa, datele sunt interesante, iar utilitatea potențială a acestora pare excelentă. Și, bineînțeles, va fi ușor să extindeți colectarea și analiza datelor utilizând codul fundamental pe care l-am împărtășit astăzi cu dvs..
Apropo, procentul de utilizatori invitați care își planifică propriile întâlniri este de aproximativ 9% (dar este un set de date mic).
Probabil că vă întrebați dacă putem înregistra aceste coloane. Sper că o voi aborda într-un tutorial de urmărire, care necesită întotdeauna interacțiunea cu zeița editorială. Doar FYI, nu toată lumea se îndepărtează de aceste conversații. De asemenea, o voi cere să îmi permită să scriu despre caracteristici de administrare, cum ar fi dezactivarea utilizatorilor, respingerea parolelor etc..
Dacă nu mai auziți de la mine, știți că Domnul Luminii mi-a găsit o utilizare.
Ce urmeaza?
Așa cum am menționat, lucrez în mod fierbinte pentru a pregăti Planificatorul întâlnirilor pentru eliberarea alfa. Mă concentrez în primul rând pe îmbunătățirile și caracteristicile cheie care vor face ca eliberarea alfa să meargă fără probleme.
Urmărim totul în Asana acum, despre care voi scrie despre un tutorial viitoare; a fost incredibil de util. Există, de asemenea, câteva noi caracteristici interesante încă pe drum. (Ca un profesor de yoga, cred că Asana este cel mai rău nume de produs din toate timpurile. Ei au luat în mod obișnuit un termen comun în yoga pronunțat āsana sau ah-sana și au schimbat pronunția în saună - și au pus asta în videoclipurile lor introductive. nu a fost ușor să consultăm anul trecut vorbind cu membrii echipei de clienți despre ceea ce au pus într-o saună și să vorbească cu yoghinii despre āsana.
De asemenea, încep să mă concentrez mai mult pe efortul de colectare a investițiilor viitoare împreună cu Planificatorul de întâlniri. Tocmai încep să experimentez WeFunder pe baza punerii în aplicare a noilor reguli de cultură a SEC. Vă rugăm să luați în considerare profilul nostru. Voi scrie, de asemenea, mai multe despre acest lucru într-un viitor tutorial.
Din nou, în timp ce așteptați mai multe episoade, programați prima dvs. întâlnire și încercați șabloanele cu prietenii dvs. cu cutiile poștale Gmail. De asemenea, aș aprecia dacă v-ați împărtășit experiența de mai jos în comentariile dvs. și întotdeauna mă interesează sugestiile dvs. Puteți să mă contactați și pe Twitter @reifman direct. De asemenea, le puteți posta pe site-ul de asistență Planificator de întâlniri.
Uita-te pentru viitoarele tutoriale în Building Your Startup cu seria PHP.
Link-uri conexe
- Planificatorul întâlnirilor
- Întâlnirea cu planificatorul de întâlniri
- Programarea cu Yii2: Noțiuni de bază
- Schimbul de dezvoltatori Yii2
- Pronunțând Asana