Carita ngeunaan hiji proyék atanapi kumaha kuring nyéépkeun 7 taun nyiptakeun PBX dumasar kana Asterisk sareng Php

Pasti seueur anjeun, sapertos kuring, gaduh ide pikeun ngalakukeun hiji hal anu unik. Dina tulisan ieu kuring bakal ngajelaskeun masalah téknis sareng solusi anu kuring kedah nyanghareupan nalika ngembangkeun PBX. Sugan ieu bakal nulungan batur mutuskeun dina gagasan sorangan, sarta batur nuturkeun jalur well-trodden, sabab kuring ogé benefited tina pangalaman panaratas.

Carita ngeunaan hiji proyék atanapi kumaha kuring nyéépkeun 7 taun nyiptakeun PBX dumasar kana Asterisk sareng Php

Gagasan sareng syarat konci

Sarta eta sadayana dimimitian saukur ku cinta pikeun tanda asterik (kerangka pikeun ngawangun aplikasi komunikasi), automation telepon sareng pamasangan freepbx (antarmuka wéb pikeun tanda asterik). Upami kabutuhan perusahaan henteu spésifik sareng murag dina kamampuan freepbx - sagalana hébat. Sakabéh instalasi lumangsung dina XNUMX jam, parusahaan narima PBX ngonpigurasi, panganteur ramah-pamaké sarta latihan pondok tambah rojongan upami hoyong.

Tapi tugas paling narik éta non-standar lajeng éta teu jadi fabulous. tanda asterik tiasa ngalakukeun loba, tapi tetep panganteur web dina urutan gawé, ieu diperlukeun méakkeun sababaraha kali leuwih waktos. Janten detil leutik tiasa langkung lami tibatan masang sesa PBX. Sareng intina sanés yén peryogi waktos anu lami pikeun nyerat antarmuka wéb, tapi titikna aya dina fitur arsitéktur. freepbx. Arsitéktur pendekatan jeung métode freepbx ieu diteundeun kaluar dina waktu php4, sarta dina momen éta geus aya php5.6 on nu sagalana bisa dijieun basajan tur leuwih merenah.

Anu terakhir nyaéta dialplans grafis dina bentuk diagram. Nalika kuring diusahakeun ngawangun hal kawas kieu pikeun freepbx, Kuring sadar yen kuring kudu nyata nulis ulang eta bakal leuwih gampang pikeun ngawangun hal anyar.

Sarat konci éta:

  • setelan basajan, intuitif diaksés malah ka administrator novice. Ku kituna, pausahaan teu merlukeun pangropéa PBX di sisi kami,
  • modifikasi gampang supados tugas direngsekeun dina waktos anu cekap,
  • betah integrasi jeung PBX. U freepbx euweuh API pikeun ngarobah setelan, i.e. Anjeun teu bisa, contona, nyieun grup atawa ménu sora tina aplikasi pihak katilu, ngan API sorangan tanda asterik,
  • opensource - pikeun programer ieu penting pisan pikeun modifikasi pikeun klien.

Gagasan pangwangunan anu langkung gancang nyaéta ngagaduhan sadaya pungsionalitasna diwangun ku modul dina bentuk objék. Sadaya obyék kedah gaduh kelas indungna anu umum, anu hartosna nami sadaya fungsi utama parantos dipikanyaho sareng ku kituna parantos aya palaksanaan standar. Objék bakal ngidinan Anjeun pikeun nyirorot ngurangan jumlah argumen dina bentuk arrays associative kalawan kenop string, nu bisa manggihan di freepbx Ieu mungkin ku examining sakabéh fungsi jeung fungsi nested. Dina kasus objék, autocompletion banal bakal némbongkeun sakabéh sipat, sarta sacara umum bakal simplify hirup sababaraha kali leuwih. Tambih Deui, warisan sareng definisi ulang parantos ngarengsekeun seueur masalah sareng modifikasi.

Hal salajengna anu ngalambatkeun waktos rework sareng kedah dihindari nyaéta duplikasi. Upami aya modul anu tanggung jawab pikeun nelepon karyawan, maka sadaya modul sanés anu kedah ngirim telepon ka karyawan kedah nganggo éta, sareng henteu nyiptakeun salinan sorangan. Janten, upami anjeun kedah ngarobih, maka anjeun ngan ukur kedah robih dina hiji tempat sareng milarian "kumaha éta jalanna" kedah dilaksanakeun di hiji tempat, sareng henteu milarian sapanjang proyék.

Vérsi munggaran sareng kasalahan munggaran

Prototipe munggaran geus siap dina sataun. Sakabéh PBX, sakumaha rencanana, éta modular, sarta modul teu ukur bisa nambahkeun fungsionalitas anyar pikeun nelepon processing, tapi ogé ngarobah panganteur web sorangan.

Carita ngeunaan hiji proyék atanapi kumaha kuring nyéépkeun 7 taun nyiptakeun PBX dumasar kana Asterisk sareng Php
Leres, ideu ngawangun dialplan dina bentuk skéma sapertos kitu sanés milik kuring, tapi éta gampang pisan sareng kuring ngalakukeun anu sami pikeun tanda asterik.

Carita ngeunaan hiji proyék atanapi kumaha kuring nyéépkeun 7 taun nyiptakeun PBX dumasar kana Asterisk sareng Php

Ku nulis modul, programer geus bisa:

  • jieun pungsionalitas anjeun sorangan pikeun ngolah telepon, anu tiasa disimpen dina diagram, ogé dina ménu unsur di kénca,
  • jieun halaman anjeun sorangan pikeun antarmuka wéb sareng tambahkeun témplat anjeun ka halaman anu tos aya (upami pamekar halaman parantos nyayogikeun ieu),
  • tambahkeun setelan anjeun ka tab setelan utama atawa jieun tab setelan anjeun sorangan,
  • programmer bisa inherit ti modul aya, ngarobah bagian tina fungsionalitas tur ngadaptar eta dina ngaran anyar atawa ngaganti modul aslina.

Salaku conto, ieu kumaha anjeun tiasa nyiptakeun ménu sora anjeun nyalira:

......
class CPBX_MYIVR extends CPBX_IVR
{
 function __construct()
 {
 parent::__construct();
 $this->_module = "myivr";
 }
}
.....
$myIvrModule = new CPBX_MYIVR();
CPBXEngine::getInstance()->registerModule($myIvrModule,__DIR__); //Зарегистрировать новый модуль
CPBXEngine::getInstance()->registerModuleExtension($myIvrModule,'ivr',__DIR__); //Подменить существующий модуль

The implementations kompléks munggaran dibawa kareueus munggaran jeung disappointments munggaran. Kuring éta bungah yén éta digawé, yén kuring geus bisa baranahan fitur utama freepbx. Abdi bungah yén jalma-jalma resep kana ide skéma éta. Masih seueur pilihan pikeun nyederhanakeun pamekaran, tapi dina waktos éta sababaraha pancén parantos digampangkeun.

API pikeun ngarobih konfigurasi PBX mangrupikeun kuciwa - hasilna henteu pisan anu dipikahoyong. Kuring nyandak prinsip anu sami sareng di freepbx, ku ngaklik tombol Larapkeun, sakabéh konfigurasi dijieun deui jeung modul nu restarted.

Sigana mah kieu:

Carita ngeunaan hiji proyék atanapi kumaha kuring nyéépkeun 7 taun nyiptakeun PBX dumasar kana Asterisk sareng Php
*Dialplan mangrupikeun aturan (algoritma) dimana telepon diolah.

Tapi kalayan pilihan ieu, mustahil nulis API normal pikeun ngarobah setelan PBX. Kahiji, operasi nerapkeun parobahan ka tanda asterik panjang teuing jeung sumberdaya intensif.
Bréh, anjeun teu bisa nelepon dua fungsi dina waktos anu sareng, sabab duanana bakal nyieun konfigurasi.
Katilu, éta nerapkeun sadaya setélan, kalebet anu dilakukeun ku pangurus.

Dina versi ieu, sakumaha dina Askozia, ieu mungkin keur ngahasilkeun konfigurasi modul ukur robah na balikan deui ukur modul diperlukeun, tapi ieu sadayana satengah ukuran. Ieu diperlukeun pikeun ngarobah pendekatan.

Vérsi kadua. Irung ditarik kaluar buntut nyangkut

Gagasan pikeun ngabéréskeun masalah éta henteu nyiptakeun deui konfigurasi sareng dialplan pikeun tanda asterik, tapi nyimpen informasi kana database jeung maca tina database langsung bari ngolah sauran. tanda asterik Abdi parantos terang kumaha maca konfigurasi tina pangkalan data, ngan ukur robih nilai dina pangkalan data sareng sauran salajengna bakal diolah kalayan tumut kana parobihan, sareng fungsina sampurna pikeun maca parameter dialplan. REALTIME_HASH.

Dina tungtungna, teu perlu malah balikan deui tanda asterik lamun ngarobah setelan sareng sadaya setelan mimiti dilarapkeun langsung ka tanda asterik.

Carita ngeunaan hiji proyék atanapi kumaha kuring nyéépkeun 7 taun nyiptakeun PBX dumasar kana Asterisk sareng Php

Hiji-hijina parobahan dina dialplan mangrupakeun tambahan nomer extension na petunjuk. Tapi ieu parobahan titik leutik

exten=>101,1,GoSub(‘sub-callusers’,s,1(1)); - точечное изменение, добавляется/изменяется через ami

; sub-callusers – универсальная функция генерится при установке модуля.
[sub-callusers]
exten =>s,1,Noop()
exten =>s,n,Set(LOCAL(TOUSERID)=${ARG1})
exten =>s,n,ClearHash(TOUSERPARAM)
exten =>s,n,Set(HASH(TOUSERPARAM)=${REALTIME_HASH(rl_users,id,${LOCAL(TOUSERID)})})
exten =>s,n,GotoIf($["${HASH(TOUSERPARAM,id)}"=""]?return)
...

Anjeun tiasa sacara gampil nambahkeun atawa ngarobah hiji garis dina dialplan ngagunakeun Ami (antarmuka kontrol tanda asterik) sareng teu aya reboot sadaya rencana telepon anu diperyogikeun.

Ieu direngsekeun masalah sareng API konfigurasi. Anjeun malah tiasa langsung lebet kana pangkalan data sareng nambihan grup énggal atanapi robih, contona, waktos dial-up dina kolom "dialtime" pikeun grup sareng sauran salajengna bakal salami waktos anu ditangtukeun (Ieu sanés rekomendasi pikeun aksi, saprak sababaraha operasi API merlukeun Ami nelepon).

The palaksanaan hésé munggaran deui mawa kareueus munggaran tur kuciwa. Kuring éta bungah yén éta digawé. Pangkalan data janten tautan kritis, gumantungna kana disk ningkat, langkung seueur résiko, tapi sadayana damel sacara stabil sareng tanpa masalah. Sareng anu paling penting, ayeuna sadayana anu tiasa dilakukeun ngaliwatan antarmuka wéb tiasa dilakukeun ngalangkungan API, sareng metode anu sami dianggo. Salaku tambahan, antarbeungeut wéb ngaleungitkeun tombol "larapkeun setélan kana PBX", anu sering hilap ku pangurus.

Kuciwa éta pamekaran janten langkung rumit. Kusabab versi munggaran, basa PHP geus dihasilkeun dialplan dina basa tanda asterik sarta eta Sigana lengkep unreadable, ditambah basa sorangan tanda asterik pikeun nulis dialplan éta pisan primitif.

Naon rupana:

$usersInitSection = $dialplan->createExtSection('usersinit-sub','s');
$usersInitSection
 ->add('',new Dialplanext_gotoif('$["${G_USERINIT}"="1"]','exit'))
 ->add('',new Dialplanext_set('G_USERINIT','1'))
 ->add('',new Dialplanext_gosub('1','s','sub-AddOnAnswerSub','usersconnected-sub'))
 ->add('',new Dialplanext_gosub('1','s','sub-AddOnPredoDialSub','usersinitondial-sub'))
 ->add('',new Dialplanext_set('LOCAL(TECH)','${CUT(CHANNEL(name),/,1)}'))
 ->add('',new Dialplanext_gotoif('$["${LOCAL(TECH)}"="SIP"]','sipdev'))
 ->add('',new Dialplanext_gotoif('$["${LOCAL(TECH)}"="PJSIP"]','pjsipdev'))

Dina vérsi kadua, dialplan janten universal, kalebet sadaya pilihan pamrosésan anu mungkin gumantung kana parameter sareng ukuranana ningkat sacara signifikan. Sadaya ieu pisan ngalambatkeun waktos pangwangunan, sareng panginten yén sakali deui kedah ngaganggu dialplan ngajantenkeun kuring sedih.

Vérsi katilu

Gagasan pikeun ngabéréskeun masalah éta henteu ngahasilkeun tanda asterik dialplan ti php jeung pamakéan FastAGI jeung nulis sagala aturan processing dina PHP sorangan. FastAGI Hal ieu ngamungkinkeun tanda asterik, pikeun ngolah telepon, sambungkeun kana stop kontak. Nampi paréntah ti dinya sareng kirimkeun hasil. Ku kituna, logika dialplan geus di luar wates tanda asterik tur bisa ditulis dina basa naon, bisi kuring dina PHP.

Aya loba trial and error. Masalah utama nyaéta yén kuring parantos ngagaduhan seueur kelas / file. Butuh waktu ngeunaan 1,5 detik pikeun nyieun objék, initialize aranjeunna, sarta ngadaptar silih saling, sarta reureuh ieu per panggero teu hal anu bisa dipaliré.

Initialization kudu lumangsung ngan sakali sahingga milarian solusi dimimitian ku nulis layanan dina php ngagunakeun Pthreads. Saatos saminggu ékspérimén, pilihan ieu ditunda kusabab intricacies kumaha ekstensi ieu jalan. Saatos sabulan uji coba, kuring ogé kedah ngantunkeun program asynchronous dina PHP; Abdi peryogi anu saderhana, wawuh ka pemula PHP, sareng seueur ekstensi pikeun PHP anu sinkron.

Leyuran éta layanan multi-threaded urang sorangan di C, nu ieu disusun kalawan PHPLIB. Éta ngamuat sadaya file php ATS, ngantosan sadayana modul diinisialisasi, nambihan callback ka silih, sareng nalika sadayana parantos siap, cache éta. Nalika inquiring ku FastAGI stream a dijieun, salinan tina cache sadaya kelas na data dihasilkeun di dinya, sarta pamundut disalurkeun kana fungsi php.

Kalayan solusi ieu, waktos ti ngirim telepon ka layanan kami ka paréntah munggaran tanda asterik turun tina 1,5s ka 0,05s sarta waktu ieu rada gumantung kana ukuran proyek.

Carita ngeunaan hiji proyék atanapi kumaha kuring nyéépkeun 7 taun nyiptakeun PBX dumasar kana Asterisk sareng Php

Hasilna, waktu pikeun ngembangkeun dialplan diréduksi sacara signifikan, sarta kuring bisa ngahargaan ieu saprak kuring kungsi nulis balik sakabéh dialplan sadaya modul dina PHP. Firstly, métode kudu geus ditulis dina php pikeun ménta hiji obyék tina database; maranéhanana diperlukeun pikeun tampilan dina panganteur web, sarta Bréh, sarta ieu hal utama, éta tungtungna mungkin mun merenah pikeun digawe sareng string kalawan angka na arrays. kalawan database tambah loba ekstensi PHP.

Pikeun ngolah dialplan di kelas modul anjeun kudu nerapkeun fungsi dialplanDynamicCall jeung argumen pbxCallRequest bakal ngandung hiji obyék pikeun berinteraksi sareng tanda asterik.

Carita ngeunaan hiji proyék atanapi kumaha kuring nyéépkeun 7 taun nyiptakeun PBX dumasar kana Asterisk sareng Php

Salaku tambahan, janten mungkin pikeun debug dialplan (php gaduh xdebug sareng tiasa dianggo pikeun jasa kami), anjeun tiasa ngaléngkah léngkah-léngkah ku ningali nilai-nilai variabel.

Data nelepon

Sakur analytics sareng laporan peryogi data anu dikumpulkeun leres, sareng blok PBX ieu ogé ngalangkungan seueur percobaan sareng kasalahan ti mimiti dugi ka versi katilu. Seringna, data telepon mangrupikeun tanda. Hiji panggero = hiji rekaman: anu nelepon, anu ngajawab, sabaraha lila maranéhna ngobrol. Dina pilihan anu langkung narik, aya tanda tambahan anu nunjukkeun karyawan PBX anu disebut nalika nelepon. Tapi kabeh ieu nyertakeun ukur bagian tina kabutuhan.

Syarat awal nyaéta:

  • simpen teu ngan anu PBX disebut, tapi ogé anu diwaler, sabab aya interceptions sareng ieu kedah diperhatoskeun nalika nganalisa telepon,
  • waktos sateuacan nyambungkeun sareng karyawan. Di freepbx jeung sababaraha PBX séjén, nelepon dianggap diwaler pas PBX nyokot telepon. Tapi pikeun ménu sora anjeun kedah angkat telepon, janten sadaya telepon dijawab sareng waktos ngantosan jawaban janten 0-1 detik. Ku alatan éta, ieu mutuskeun pikeun ngahemat teu ngan waktu saméméh respon a, tapi waktu saméméh nyambungkeun jeung modul konci (modul sorangan susunan bandéra ieu. Ayeuna éta "Pagawe", "Jalur éksternal"),
  • pikeun dialplan leuwih kompleks, lamun nelepon ngarambat antara grup béda, ieu diperlukeun pikeun bisa nalungtik unggal unsur misah.

Pilihan anu pangsaéna nyaéta nalika modul PBX ngirim inpormasi ngeunaan dirina dina telepon sareng pamustunganana nyimpen inpormasi dina bentuk tangkal.

Sigana mah kieu:

Kahiji, informasi umum ngeunaan panggero (kawas dulur sejenna - euweuh husus).

Carita ngeunaan hiji proyék atanapi kumaha kuring nyéépkeun 7 taun nyiptakeun PBX dumasar kana Asterisk sareng Php

  1. Narima telepon di jalur luar"Pikeun ujian"Jam 05:55:52 ti nomer 89295671458 dugi ka nomer 89999999999, tungtungna dijawab ku karyawan"Sekretaris2»kalawan nomer 104. Klien waited 60 detik sarta dikaitkeun pikeun 36 detik.
  2. Pagawe"Sekretaris2"nelepon ka 112 sareng karyawan ngajawab"Pangurus1»sanggeus 8 detik. Aranjeunna ngobrol pikeun 14 detik.
  3. Klién ditransferkeun ka Karyawan "manajer1"Dimana aranjeunna neraskeun ngobrol salami 13 detik

Tapi ieu mangrupikeun puncak gunung es; pikeun unggal catetan anjeun tiasa nampi riwayat telepon anu lengkep ngalangkungan PBX.

Carita ngeunaan hiji proyék atanapi kumaha kuring nyéépkeun 7 taun nyiptakeun PBX dumasar kana Asterisk sareng Php

Sadaya inpormasi dibere salaku sarang telepon:

  1. Narima telepon di jalur luar"Pikeun ujian» jam 05:55:52 ti nomer 89295671458 ka nomer 89999999999.
  2. Jam 05:55:53 jalur luar ngirim telepon ka sirkuit Incoming "ujian»
  3. Nalika ngolah sauran nurutkeun skema, modul "panggero manajer", nu nelepon téh 16 detik. Ieu mangrupikeun modul anu dikembangkeun pikeun klien.
  4. Modul"panggero manajer"Ngintunkeun telepon ka karyawan anu tanggung jawab kana nomer (klien)"Pangurus1” sareng ngantosan 5 detik kanggo réspon. Gerentes teu ngajawab.
  5. Modul"panggero manajer"Ngintun telepon ka grup"manajer CORP" Ieu manajer séjén tina arah anu sarua (linggih di kamar anu sarua) sarta ngantosan 11 detik pikeun respon a.
  6. Kelompok "manajer CORP"nelepon karyawan"Pangurus1, Pangurus2, Pangurus3"sakaligus 11 detik. Taya jawaban.
  7. Telepon gerentes réngsé. Sareng sirkuit ngirim telepon ka modul "Milih jalur tina 1c" Ogé modul ditulis pikeun klien. Di dieu telepon diprosés pikeun 0 detik.
  8. Sirkuit ngirim telepon ka menu sora "Dasar sareng telepon tambahan" Klién ngantosan di dinya salami 31 detik, teu aya telepon tambahan.
  9. Skéma ngirim telepon ka Grup "sekretaris", dimana klien ngantosan 12 detik.
  10. Dina hiji grup, 2 pagawé disebut sakaligus "Sekretaris1"Jeung"Sekretaris2"Sareng saatos 12 detik karyawan ngajawab"Sekretaris2" Waleran kana telepon digandakeun kana telepon indungna. Tétéla di grup anjeunna ngajawab "Sekretaris2", nalika nelepon sirkuit ngajawab "Sekretaris2"sarta ngawalon telepon di jalur luar ku"Sekretaris2".

Éta nyimpen inpormasi ngeunaan unggal operasi sareng nyarangna anu bakal ngamungkinkeun anjeun ngan ukur ngadamel laporan. Laporan dina ménu sora bakal ngabantosan anjeun terang sabaraha éta ngabantosan atanapi ngahalangan. Nyusun laporan telepon lasut ku pagawé, nyokot kana akun yén panggero ieu disadap sahingga teu dianggap lasut, sarta nyokot kana akun yén éta téh panggero grup, sarta batur diwaler saméméhna, nu hartina panggero ieu ogé teu lasut.

Panyimpen inpormasi sapertos kitu bakal ngamungkinkeun anjeun nyandak unggal grup sacara misah sareng nangtoskeun kumaha efektifna jalanna, sareng ngawangun grafik grup anu diwaler sareng lasut dumasar jam. Anjeun ogé tiasa pariksa kumaha akurat sambungan ka manajer anu tanggung jawab ku nganalisa transfer saatos nyambung ka manajer.

Anjeun oge bisa ngalakukeun studi rada atypical, contona, sabaraha sering nomer nu teu aya dina database dipencét extension bener atawa sabaraha perséntase telepon kaluar diteruskeun kana handphone.

Naon hasilna?

Spésialis henteu diperyogikeun pikeun ngajaga PBX, pangurus anu paling biasa tiasa ngalakukeun - diuji dina prakték.

Pikeun modifikasi, spesialis anu ngagaduhan kualifikasi anu serius henteu diperyogikeun; pangaweruh ngeunaan PHP cekap, sabab Modul parantos ditulis pikeun protokol SIP, sareng pikeun antrian, sareng pikeun nelepon karyawan, sareng anu sanésna. Aya kelas wrapper pikeun tanda asterik. Pikeun ngembangkeun modul, programer tiasa (sareng ku cara anu saé) nyauran modul anu siap-siap. Jeung pangaweruh tanda asterik sagemblengna teu perlu lamun klien nanya pikeun nambahkeun hiji kaca kalawan sababaraha laporan anyar. Tapi prakna nunjukeun yen sanajan programer pihak-katilu bisa Cope, aranjeunna ngarasa teu aman tanpa dokuméntasi jeung sinyalna normal tina komentar, jadi aya kénéh kamar pikeun perbaikan.

Modul tiasa:

  • nyiptakeun kamampuan ngolah sauran énggal,
  • tambahkeun blok anyar kana antarmuka wéb,
  • warisan ti modul mana wae nu aya, ngartikeun ulang fungsi jeung ngaganti eta, atawa ngan saukur salinan rada dirobah,
  • tambahkeun setélan anjeun kana citakan setélan modul anu sanés sareng seueur deui.

Setélan PBX via API. Sakumaha ditétélakeun di luhur, sadaya setelan disimpen dina database jeung maca dina waktu nelepon, jadi Anjeun bisa ngarobah sagala setelan PBX ngaliwatan API. Nalika nyauran API, konfigurasi henteu didamel deui sareng modul henteu di-restart, janten henteu masalah sabaraha setélan sareng karyawan anu anjeun gaduh. Paménta API dieksekusi gancang sareng henteu saling meungpeuk.

PBX nyimpen sadaya operasi konci kalayan telepon kalayan durasi (ngantosan / paguneman), nyarang sareng dina istilah PBX (pagawe, grup, jalur éksternal, sanés saluran, nomer). Hal ieu ngamungkinkeun anjeun ngawangun rupa-rupa laporan pikeun klien khusus sareng seueur padamelan nyaéta nyiptakeun antarmuka anu ramah-pamaké.

Waktos bakal nyarios naon anu bakal kajadian salajengna. Masih aya seueur nuansa anu kedah dirobih deui, masih seueur rencana, tapi sataun parantos kalangkung ti nyiptakeun versi 3rd sareng urang parantos tiasa nyarios yén idena jalan. Karugian utama versi 3 nyaéta sumberdaya hardware, tapi ieu biasana anu anjeun kedah mayar pikeun ngagampangkeun pangwangunan.

sumber: www.habr.com

Tambahkeun komentar