WYŻSZA SZKOŁA
INFORMATYKI I ZARZĄDZANIA
Z SIEDZIBĄ W
R
ZESZOWIE
WYDZIAŁ ADMINISTRACYJNO-INFORMATYCZNY
Kierunek:
INFORMATYKA I EKONOMETRIA
Specjalność:
S
YSTEMY I SIECI KOMPUTEROWE
PRACA DYPLOMOWA
P
ROMOTOR
DR INŻ
. J
ANUSZ
Ś
WIERZOWICZ
Rzeszów 2001
2
Wstęp
Przedstawiony projekt systemu internetowej sprzedaży towarów jest fragmentem całości
serwisu internetowego firmy. Został on zbudowany przy wykorzystaniu narzędzi
i oprogramowania OpenSource. Korzysta z istniejących w aplikacjach finansowo księgowych
danych, gromadzonych w codziennej działalności przedsiębiorstwa. Za kryterium
podstawowe przyjęto ekonomiczne dostosowanie tworzonych rozwiązań pod kątem
zastosowania takiego modelu sklepu internetowego w firmach małej i średniej wielkości.
Kod aplikacji jest wysoce elastyczny i pozwala na swobodne modyfikowanie zarówno
formatu wejścia danych jak i interfejsu użytkownika oraz wkomponowanie w istniejący
serwis internetowy firmy. Aktualna działająca aplikacja sklepu internetowego znajduje się
pod adresem:
gdzie jest użytkowana i stale rozwijana. W rozwój aplikacji
sklepu internetowego oraz całości serwisu firmy duży wkład pracy stale wnosi mój przyjaciel
Radosław Wierzbicki za co niniejszym składam mu podziękowanie.
3
Cel i zakres pracy
Rozdział I omawia założenia wstępne, przedstawia charakter pracy oraz metody i narzędzia
zastosowane w budowie aplikacji. Omówiono tutaj uwarunkowania ekonomiczne
powstającego systemu oraz zaprezentowano bazę systemową oprogramowania.
Rozdział II przedstawia teorię i funkcjonowanie sklepu internetowego, ukazuje wymagania
użytkowników dotyczące działania serwisów internetowych firm. Przedstawia wpływ
marketingu i reklamy na popularność serwisu. Prezentuje zasady działania oraz model
logiczny i funkcjonalny tego typu aplikacji internetowych.
Rozdział III prezentuje przygotowanie danych na potrzeby sklepu internetowego.
Przedstawia sposoby konwersji, archiwizacji i przesyłania danych pomiędzy serwerami
i aplikacjami istniejącymi w przedsiębiorstwie. Pokazuje procedury konwersji i obróbki
danych oraz ich automatyczny załadunek do systemu bazodanowego.
Rozdział IV omawia strukturę i organizację bazy danych sklepu. Prezentuje skrypty
administracyjne i bazodanowe systemu oraz omawia ich budowę i działanie. Przedstawia
podział funkcjonalny konstruowanych podprocedur oraz strukturę bazy SQL.
Rozdział V prezentuje wyszukiwarkę towarów, koncepcję i zasadę działania oraz kod
źródłowy. Omawia napotykane problemy optymalizacji zapytań do bazy danych i sposoby
ich rozwiązywania.
Rozdział VI przedstawia dalsze cele i kierunki rozwoju projektu sklepu internetowego.
Omawia budowę interfejsu rejestracji informacji dodatkowych o towarach oraz model
i zasadę działania „koszyka” na towary. Prezentuje stosowane metody śledzenia
i utrzymywania sesji pomiędzy webserwerem a przeglądarką klienta.
Podsumowanie pracy omawia zebrane podczas budowy projektu wnioski i doświadczenia.
4
Rozdział I – Z
AŁOŻEN IA WSTĘPNE
,
PRZEDSTAWIENIE METOD
I
NARZĘDZI
U
WARUNKOWANIA EKONOMICZNE
W czasach gwałtownego rozwoju Internetu, działalność gospodarcza wkracza w nowy etap
rozwoju. Nowe możliwości jakie stwarza zasięg działania, powszechność oraz dostępność
Internetu inspirują do zainteresowania się rozwojem biznesu na tej platformie. Szczególną
dziedziną, w której jest możliwy szybki rozwój i poszerzenie podstawowej i tradycyjnej
działalności jest handel, rozumiany zarówno jako wymiana pomiędzy partnerami jak
i dostawcami a ich klientami. Rozwój małej i średniej przedsiębiorczości wymaga rozwiązań
dostosowanych do potrzeb tego segmentu działalności komercyjnej. Rynek ten w krajach
Unii Europejskiej czy też w Stanach Zjednoczonych jest określany jako SOHO (Small Office
Home Office). Podstawowym kryterium jest tu wielkość firmy, różnie rozumiana, często
jako ilość zatrudnionych pracowników czy też wielkość generowanych przychodów,
obrotów itp. Jedyną różnicą pomiędzy Polską a innymi krajami Europy Zachodniej jest
właśnie skala według której następuje segmentacja. W Polsce przyjmuje się że średniej
wielkości przedsiębiorstwo zatrudnia ponad dwudziestu pięciu pracowników. Jest to o rząd
wielkości mniejsza liczba niż w innych krajach Unii Europejskiej. Analiza potrzeb takich firm
wykazała, że podstawowym kryterium wyboru rozwiązań informatycznych wspierających
i rozszerzających działalność podstawową są koszty zarówno zakupu, rozwoju jak
i
utrzymania systemów, oprogramowania i administracji. Społeczność internetowa
jednoznacznie wykazuje ogromne zainteresowanie rozwojem oprogramowania i systemów
) zarówno opartych na licencji GNU GPL jak i BSD.
Teksty tych dokumentów umieszczone są pod adresem
Przykładem systemów OpenSource jest np. Linux czy wiele innego doskonałego
oprogramowania określanego wspólną nazwą GNU. Kolejnym elementem jest także cała
rodzina Unix’owych sieciowych systemów operacyjnych *BSD takich jak
FreeBSD (
), języków programowania jak
PHP (
) i innych. Tendencje te ostatnio znalazły poparcie u „wielkich” rynku
informatycznego. Sun wykupił i udostępnił na licencji GNU pakiet biurowy StarOffice, IBM
przeniósł Linux'a na swoje platformy mainframe, Borland stworzył środowisko
Delphi/Kylix do budowy uniwersalnych aplikacji dla środowiska Windows/Linux oraz
5
udostępnił bazę InterBase itp. Powstała organizacja Free Software Foundation
(
) wspierająca ten „ruch” w kierunku dalszego upowszechniania
i popularyzowania tego trendu. Bazując na osiągnięciach społeczności internetowej w tej
dziedzinie za podstawę realizacji systemu umożliwiającego małym i średnim firmom
wkroczenie w sferę e-biznesu przyjęto systemy bazujące na tego typu oprogramowaniu.
P
REZENTACJA BAZY SYSTEMOWEJ OPROGRAMOWANIA
Na podstawę do budowy systemu kompleksowo obsługującego całość działań
komunikacyjnych, sieciowych, aplikacyjnych a jednocześnie zapewniającego wysoki stopień
bezpieczeństwa całego systemu został wybrany system Unix z rodziny BSD. FreeBSD jest
systemem dedykowanym i optymalizowanym do pracy na platformie Intel (IA32) oraz
Alpha. Jest on z powodzeniem stosowany przez największych dostawców usług
internetowych i przeznaczony jest do pracy przy bardzo dużych obciążeniach. Przykładem
jego wykorzystania są serwisy web i email np.
, największy serwis
ftp
. Na serwerze firmy gdzie wykonano wdrożenie
kompletnego środowiska obsługującego całość działalności internetowej zainstalowany
został Unix FreeBSD, serwer pocztowy Qmail (
poczty internetowej (nawet do kilku milionów przesyłek dziennie), webserwer
) do obsługi serwisów WWW oraz dostępu do poczty poprzez
przeglądarkę na bazie TWIG (
) a także obsługi sklepu internetowego
działającego na bazie PostgreSQL (
), do której dostęp i dynamicznie
budowane strony wykonano w PHP4 (
). Gwałtownie rosnącą popularność tego
języka przedstawia Rysunek 1.
Rysunek 1 – Popularność języka PHP.
6
Serwer internetowy świadczy ponadto wiele innych usług i zabezpieczony został firewallem.
Analogicznie, po stronie wewnętrznego LAN firmy został uruchomiony drugi serwer na
bazie FreeBSD obsługujący kilka podsieci firmy oraz zapewniający dostęp do internetu
z wewnątrz firmy. Na serwerze w celu poprawienia wewnętrznego bezpieczeństwa sieci
również uruchomiono firewall oraz translację adresów. Sieć firmowa oparta jest na stacjach
roboczych z oprogramowaniem MS Windows natomiast na serwerze FreeBSD sieć
Windows jest obsługiwana przez oprogramowanie Samba
Oprogramowanie finansowo księgowe stworzone przez firmę Comvar (
na bazie CA Clipper rezyduje na dyskach sieciowych wewnętrznego serwera i tam
przechowywane są dane. Taka organizacja pozwoliła na wykorzystanie danych firmy do
automatycznego zasilania sklepu internetowego a ponadto udostępniła całej firmie
wymagane usługi internetowe przy minimalnych kosztach, bowiem całe oprogramowanie
serwerów bazuje na OpenSource. Schemat działania całości struktury sieciowej firmy
sprawdził się w ciągu kilku lat jego funkcjonowania i gwarantuje wysoką jakość
i niezawodność działania. Na serwerach dokonywana jest regularna archiwizacja wszystkich
danych firmy a samo środowisko FreeBSD jako wyjątkowo odporne na awarie wydaje się
być wręcz przeznaczone do takich celów. Początkowo wyjściowym systemem był Linux,
jednak problemy występujące z udostępnianiem i poprawną pracą sieci Windows oraz
podatność jego filesystemu (extfs2) na awarie wykluczyła go z dalszego stosowania, gdyż był
on tutaj najsłabszym ogniwem. Strategia ta znalazła późniejsze potwierdzenie w sytuacji, gdy
z przyczyn niezależnych od firmy cała serwerownia na skutek awarii wodociągów w budynku
została doszczętnie zalana wodą. Zniszczeniu uległy zasilacze UPS oraz router Cyclades
obsługujący łącze do internetu poprzez Polpak Frame Relay. Modemy Lucent DSLPipe
obsługujące łącza do zdalnych siedzib firmy na terenie miasta nie zostały uszkodzone.
W serwerach na skutek tej awarii zniszczeniu uległy karty sieciowe. Po wymianie kart
sieciowych w ciągu kilku godzin sieć firmowa przywrócona została do działania a żadne dane
zgromadzone na dyskach serwerów nie uległy uszkodzeniu. Poprzez zastosowanie struktury
skrzynek pocztowych natywnych dla Qmail’a określanej jako Maildir, pomimo awarii
zasilania w trakcie dostarczania poczty do skrzynek nie uległa ona uszkodzeniu. Bieżąca
działalność firmy, po krótkim czasie niezbędnym na wymianę uszkodzonych podzespołów,
mogła być kontynuowana i żadne dane nie uległy zniszczeniu, nie było nawet potrzeby
odzyskiwania ich z kopii zapasowych.
7
Rysunek 2 przedstawia schematyczną strukturę topologiczną sieci firmy:
Internet
zdalne oddziały firmy
siedziba główna firmy
strefa zdemilitaryzowana (DMZ)
Rysunek 2 – Topologia sieci przedsiębiorstwa.
8
Rozdział II – T
EORIA I FUNKCJONOWANIE
W
YMAGANIA UŻYTKOWNIKÓW
Podstawą działania sklepu internetowego są dane o towarach, na bazie których możliwe jest
dalsze jego konstruowanie. W praktyce działania sklepu dane te służą prezentacji
posiadanych produktów przeznaczonych do sprzedaży internetowym kanałem dystrybucji.
Z przeprowadzanych analiz zachowań internautów odwiedzających tego typu serwisy,
charakterystyczna jest zasada, że każda strona musi zostać załadowana w ciągu 7-10 sekund
od próby wejścia do serwisu internetowego. Dłuższy czas „odświetlania” strony zniechęca
internautów i powoduje rezygnację z dalszej eksploracji zasobów firmy. Szybkość transferów
osiągana przez najczęściej wykorzystywane do łączenia z Internetem popularne modemy
gwarantuje przepustowość w zakresie od 3KB/s do 6KB/s, stąd wynika iż ilość danych
wysyłanych przez serwer WWW do przeglądarki klienta nie powinna przekraczać 30KB do
60KB maksymalnie na podstawowe strony firmowe. Specyficzne jest natomiast podejście
internautów do stron specjalizowanych np. z prezentacjami multimedialnymi lub
szczegółowymi danymi technicznymi i użytkowymi produktów, które wzbudziły ich
zainteresowanie i o których chcą uzyskać szczegółowe informacje. Tutaj „cierpliwość”
użytkowników Internetu jest dużo większa, a ich gotowość do „czekania” na załadowanie
strony jest kilku a nawet kilkunastokrotnie większa. Takie strony mogą mieć rozmiary rzędu
100KB i więcej, o ile zbudowane są w taki sposób, że już w trakcie ich ładowania
przeglądarka wyświetla stopniowo ściągane dane. Przy największych stronach konieczne
wydaje się dzielenie prezentowanych informacji na wiele mniejszych fragmentów, po których
użytkownik może swobodnie nawigować za pomocą hyperlinków i różnego rodzaju
statycznych i dynamicznych odsyłaczy. W dobrym tonie jest konstruowanie stron „lekkich”
czyli nie przeładowanych zbędną grafiką ładującą się powoli, a przy odnośnikach do plików
z większymi obrazami czy też plikami multimedialnymi podawanie ich rozmiaru lub
przybliżonego czasu ładowania. Organizacja logiczna i wizualna strony musi być jasna
i przejrzysta dla potencjalnego klienta. Jezyk HTML opisu strony musi być zgodny ze
obowiązującymi standardami i zwyczajami stosowanymi w budowie stron. Występuje
tendencja do takiego budowania strony, aby była możliwa do poprawnej prezentacji nawet
przez najprostsze przeglądarki trybu tekstowego dostępne na platformach unixowych (jak
choćby Lynx, Links czy W3M). Oczywiście należy także wykorzystywać wszelkie nowości jak
skrypty Java czy DHTML jednak w taki sposób, aby zapewnić poprawne interpretowanie
9
strony we wszystkich najpopularniejszych przeglądarkach różnych firm i ich kilku ostatnich
wersjach. Charakterystycznym wydaje się być celowa rezygnacja w wielu serwisach
internetowych ze stosowania ramek i zastąpienie ich tabelami. Sklep internetowy ze względu
na specyfikę swojego działania, musi zapewniać bezpieczeństwo zgromadzonych danych
klientów, szczególnie gdy realizowane są płatności kartami płatniczymi. Stosowanym
standardem jest szyfrowanie transmisji takich danych przy użyciu protokołu SSL. Obecną
tendencją jest także scedowanie obowiązku weryfikacji i autoryzacji kart płatniczych na
zaufaną stronę „trzecią” w osobie organizacji zajmującej się autoryzowaniem transakcji
elektronicznych. Nowością ostatnio wprowadzaną w Polsce przez
PolCard S.A. (
), jako największego krajowego publicznego „operatora”
transakcji kartowych, jest autoryzowanie online takich transakcji, poprzez przekierowanie
transakcji autoryzacji przez sklep do serwera autoryzacji tej firmy. Analiza preferencji
użytkowników dokonujących zakupów online wykazuje jeszcze przeważającą wśród polskich
internautów strategię realizowania płatności „za zaliczeniem pocztowym”, jednak występują
pierwsze oznaki zwiększającego zainteresowania płatnościami kartą płatniczą. Związane jest
to z dużym naciskiem banków i organizowanymi promocjami zarówno przez Visa
International (
) jak i same banki na promocję i sprzedaż usług
bezgotówkowych. Często karta płatnicza jest obecnie wydawana bez opłat do każdego
zakładanego rachunku konta osobistego a bankowe tabele opłat i prowizji tak konstruowane,
aby niejako „administracyjnie” motywować klientów do korzystania z bezgotówkowych
form płatności.
M
ARKETING I REKLAMA
Ogólna zasada funkcjonowania sklepu internetowego opiera się na kilku filarch. Z jednej
strony jest to baza danych o towarach, katalogach, cennikach itp. informacjach, z drugiej są
to dane gromadzone na podstawie informacji uzyskiwanych od klientów. Wszystko to
wsparte jest analizami i statystykami oglądalności i odwiedzin na firmowych stronach serwisu
webowego. Można analizować liczbę odwiedzin, specyfikę i rodzaje poszukiwanych towarów
i informacji o nich, adresy domen z których następują wejścia oraz charakterystykę czasową
na przestrzeni dnia, tygodnia czy miesiąca lub roku. Podstawą są logi serwera jak i dane
zapisywane w bazie SQL. Analizy te pozwalają przewidywać okresy wzmożonego
zainteresowania konkretnymi produktami jak i umożliwiają na precyzowanie oferty
kierowanej do potencjalnego klienta. To daje duże pole działania w porównaniu do
10
tradycyjnych metod sprzedażowych, gdzie klient pozostaje anonimowy praktycznie aż do
momentu zawarcia konkretnej transakcji. Wsparciem dla strategii marketingowych może być
dobrze zaplanowana akcja reklamowa na bazie banerów reklamowych prezentowanych na
różnych stronach zarówno własnego serwisu jak i na zasadzie wymiany banerowej z innymi.
Wejścia poprzez kliknięcie w baner mogą być analizowane pod kątem charakterystyki
wiekowej klienta. Wiadomo bowiem gdzie dany baner był umieszczony, na jakim serwisie
i jaki jest profil internautów odwiedzających dany serwis. Nikt przecież nie będzie
reklamować drogiego sprzętu RTV na serwisach internetowych przeznaczonych dla
nastolatków. W takim przypadku właściwszym będzie zaprezentowanie sprzętu przenośnego
typu walkman czy discman. Całokształt działań promujących sklep internetowy musi być
wsparty podstawami zarówno psychologicznymi, ekonomicznymi jak i informatycznymi,
mającymi na celu osiągnięcie zamierzonego celu i sukcesu. Prezentowane rozwiązania
umożliwiają rozbudową całego serwisu o różnorakie narzędzia wpierające dział marketingu
firmy w cenne informacje, na których zebranie tradycyjnymi metodami należałoby
Rysunek 3 – Internetowa promocja towarów sklepu.
11
wydatkować zarówno duże środki finansowe jak i poświęcić sporo czasu na badania
marketingowe. Oczywistym jest także fakt, że informacje zebrane tradycyjnymi metodami
przekładają się także na sam sklep internetowy i prezentowaną ofertę. W obecnych czasach
właściwym wydaje się model działalności dodatkowej i uzupełniającej „tradycyjną”
ekonomię handlu podczas prowadzenia tego typu serwisu internetowego. W przypadku
małych i średnich przedsiębiorstw taki model może prowadzić do poszerzenia kontaktu
z potencjalnymi klientami, wyrobienia sobie dobrej marki i pozytywnej opinii wśród klientów
jak i zwiększenia sprzedaży, udziałów w rynku czy też zadowolenia klientów co może mieć
przełożenie na wymierne odnoszone korzyści.
Z
ASADA DZIAŁANIA
Zasada działania sklep internetowego została oparta o model wirtualnego środowiska
pozyskiwania informacji o produktach i w przypadku ich atrakcyjności dla klienta ewentualną
sprzedaż. Zaobserwowane zachowania dowodzą często, że wizyty na stronach służą
uzyskaniu konkretnych informacji o produkcie. Nie zawsze kończą się one zrealizowaniem
transakcji, jednak często klienci przychodzący do „tradycyjnego” sklepu firmowego
powołują się na informacje uzyskane z serwisu webowego firmy. Świadczy to o tym, iż
potencjalny klient chce być dobrze poinformowany zanim skontaktuje się już
z rzeczywistym sprzedawcą a nie jego wirtualnym odpowiednikiem. Jednak transakcje na
droższy sprzęt zawierane są w normalnym sklepie. Tutaj uwidacznia się cel działania takiego
sklepu jako bardziej informujący i promujący konkretny produkt niż nastawiony ściśle na
sprzedaż. Chcąc sprostać temu zadaniu zbudowany od podstaw sklep internetowy
udostępnia klientom wygodną wyszukiwarkę towarów, przeglądanie informacji o towarach,
nowościach i specjalnych ofertach czy promocjach. Często klienci mają już wyrobione zdanie
o konkretnych producentach i starają się dokonywać zakupów sprzętu tylko konkretnie
wybranej serii, modelu czy producenta. W tym celu przeglądarka sklepu została wzbogacona
w możliwość przeglądania tematycznego jak i w oparciu o konkretnego producenta. Chcąc
sprostać tym zadaniom, wymagana była rozbudowa modułu aktualizowania i wprowadzania
informacji dodatkowych o towarach. Oprogramowanie finansowo księgowe firmy nie było
przeznaczone do tego celu i nie udostępniało wymaganych narzędzi. Dane zgromadzone
i przetwarzane w bieżącej działalności firmy doskonale nadawały się do wykorzystania jako
wsad informacyjny o posiadanych towarach, producentach i cenach. Także informacje
o odbiorcach oraz dostawcach pozwalają na uzupełnienie sklepu o moduł kontaktów
12
ze stałymi partnerami handlowymi i rozszerzenie działalności modelu B2C (business-to-
consumer) także o model B2B (business-to-business). Jednocześnie wykorzystanie
posiadanych danych znacząco ułatwia funkcjonowanie sklepu, gdyż są one automatycznie
ładowane do bazy danych sklepu i prezentowane w serwisie webowym firmy bez
konieczności udziału pracowników. Nie jest tu wymagana specjalistyczna wiedza na temat
konstruowania zaawansowanych systemów webowych, gdyż sposób w jaki zostało to
zrealizowane jest uniwersalny i łatwo dostosowywany praktycznie do każdej aplikacji
finansowo księgowej obsługującej podstawową działalność firmy. Zwrotnie informacje ze
sklepu internetowego mogą być w łatwy sposób ładowane do modułu zamówień lub
rezerwacji w firmie, tym samym udostępniając niezbędne informacje do bieżącej działalności
firmy. Chcąc zapewnić wymagane bezpieczeństwo operacji zdecydowano się na model
wsadowego ładowania danych do sklepu oraz jego fizyczne umiejscowienie na odrębnym
serwerze. LAN-serwer lokalny ładuje dane do serwera Internetowego, gdzie są one dalej
odpowiednio obrabiane i przetwarzane a następnie prezentowane w tam zlokalizowanym
serwisie internetowym firmy. Taki logiczny podział zadań pozwolił także na oddzielenie
i rozłożenie obciążeń pomiędzy obydwa serwery uniezależniając wewnętrzną pracę firmy od
obciążenia generowanego przez internautów a także na podniesienie bezpieczeństwa
w przypadku ataków na serwer internetowy. Konieczność zapewnienia ciągłej i bezawaryjnej
pracy wewnętrznego serwera firmy była tutaj zadaniem priorytetowym.
M
ODEL LOGICZNY I FUNKCJONALNY
Opierając się na założeniach wstępnych, stworzony został model działania sklepu
internetowego jak i całokształtu „obecności” firmy w Internecie. Roboczo na wewnętrzne
potrzeby całość oprogramowania obsługującego serwis internetowy firmy otrzymała nazwę
„eMarket”. Działanie całego systemu sprowadza się do przetwarzania różnego rodzaju
danych i komunikacji sieciowej co schematycznie obrazuje Rysunek
4. Założenia
początkowe dotyczące całości systemu doprowadziły do jego logicznej budowy w sensie
topologii sieciowej oraz przepływu danych. Zasoby informacyjne firmy chronione są przed
nieuprawnionym dostępem poprzez system uprawnień zarówno do systemów finansowo
księgowych oraz na niższym poziomie dostępu do plików, katalogów i udostępnionych
drukarek sieciowych. Korzystanie z zasobów Internetu także wymaga odpowiednich
uprawnień. Ostatnia plaga wirusów pocztowych wymusiła rezygnację z mało bezpiecznych
programów pocztowych typu Microsoft Outlook oraz uruchomienie systemu pocztowego
13
w oparciu o webserwer i przeglądarki na bazie oprogramowania TWIG, które zostało
zmodyfikowane i odpowiednio dostosowane do potrzeb firmy. Spowodowało to drastyczne
zakończenie problemów z zawirusowanymi załącznikami aktywującymi się automatycznie na
skutek różnego rodzaju błędów w oprogramowaniu pocztowym firmy Microsoft. Większość
systemów, działających na serwerach firmy korzysta i współpracuje z bazą danych
PostgreSQL. Baza ta została wybrana do zastosowań wymagających dużych obciążeń jako
jedyna obsługująca standardy SQL takie jak: zaawansowane transakcje i złączenia tabel,
sekwencje, triggery, wbudowany język konstruowania funkcji i procedur uruchamianych po
stronie samej bazy danych i wiele innych nowoczesnych metod. Z bazą współpracuje
webserwer Apache, na którym wykonują się skrypty PHP generujące dynamicznie strony
korzystając ze zgromadzonych danych. Na serwerze wewnętrznym firmy cyklicznie
uruchamiają się procesy pobierające dane z baz systemu finansowo księgowego. Dane są
wstępnie konwertowane i
przygotowywane do załadowania do bazy PostgreSQL.
Wykonywana jest konwersja strony kodowej ze standardu cp852 na iso-8859-2 a gotowe do
załadunku zbiory są przesyłane na serwer internetowy firmy. Więcej informacji na temat
standardów kodowania polskich znaków można znaleźć na Polskiej Stronie
Ogonkowej (
). Tu następuje załadunek poszczególnych zbiorów do
tabel w bazie danych. Wykonywane jest indeksowanie poszczególnych pól i w tym
momencie kończy się faza dostarczenia danych dla potrzeb sklepu. Schematycznie całość
tych procesów przedstawia Rysunek 5.
Taka logiczna organizacja przepływu danych jest jakby naturalna dla topologicznej
organizacji sieci firmy jak i specyfiki działania samego oprogramowania finansowo
Zasoby sprzętowe systemów
Zbiory danych i dokumentów
Przetwarzanie
danych
Rysunek 4 – Schemat przetwarzania danych.
14
księgowego. Rozdzielenie funkcji wstepnego przygotowania danych od ich prezentowania
pozwoliło na zachowanie bezpieczeństwa zasobów zgromadzonych na wewnętrzym
serwerze firmy poprzez ich fizyczne oddzielenie od warstwy aplikacji internetowych. Dalsza
ich prezentacja obciąża serwer internetowy i nie zakłóca działania sieci wewnętrznej. Nawet
w przypadku ewentualnej awarii czy ingerencji w zasoby serwera internetowego, maszyna
“produkcyjna” kontynuuje normalne działanie bez wpływu na bieżącą pracę firmy.
Pliki DBF systemu F/K
Konwersja danych
Załadowanie do bazy SQL
Wewnętrzny serwer firmy
Internetowy serwer firmy
Rysunek 5 – Schemat procesu obiegu informacji.
15
Rozdział III – P
RZYGOT OWANIE DANYCH
KONWERSJA DANYCH
Aplikacja finansowo księgowa firmy napisana w Clipperze 5.3 przechowuje dane w postaci
plików DBF. Struktura katalogów samej aplikacji zorganizowana jest w postaci katalogu
systemu podstawowego oraz podkatalogów poszczególnych magazynów. Na podstawie
analizy przechowywanych w poszczególnych plikach danych
określono że wstępnie na potrzeby sklepu internetowego
potrzebnych będzie kilka tabel z systemu podstawowego oraz ze
wszystkich poszczególnych magazynów. Rysunek 6 przedstawia
fizyczną organizację aplikacji na dysku sieciowym widzianą od
strony użytkowników sieci Windows a z drugiej strony od
wewnątrz serwera. Z poziomu użytkownika widziana jest struktura
katalogów począwszy od wnętrza katalogu „sklep” bowiem tak
udostępniony zasób Samby jest mapowany jako dysk logiczny.
Położenie całości aplikacji magazynowej na dyskach serwera umożliwiło dostęp do tych
danych pod kątem ich konwersji dla sklepu internetowego, bez zbędnego obciążania sieci
wewnętrznej transferami a jedynie obróbką samych danych na poziomie serwera. W celu
zapewnienia integralności danych konwersja wykonuje się automatycznie w godzinach
nocnych, tak aby to nie kolidowało z działalnością firmy a jednocześnie zapewniało
aktualność danych. Charakter działania aplikacji magazynowej wymusił takie przystosowanie
modułu konwersji do struktury katalogów, aby dodanie kolejnych magazynów automatycznie
zapewniało pobieranie danych także z nich.
W celu konwersji z formatu DBF do formatu tekstowego rozpatrywano dwa warianty.
Pierwszy opierał się na konwersji skryptem napisanym w języku Perl natomiast drugi
zakładał wykorzystanie gotowego i wielokrotnie szybszego programu w języku C którego
autorem jest Brad Eacker a kod był opublikowany w 1994r w grupach USENET.
Ostatecznie przeważyła szybkość konwersji nad elastycznością rozwiązania w Perlu. Chcąc
jednak zachować możliwość dostosowywania „wyciąganych” danych z tabel DBF, wyjściowe
pliki po kompletnej konwersji DBF na TXT obrabiane są już skryptem shellowym
napisanym z wykorzystaniem natywnych unixowi narzędzi takich jak awk czy tr. Poniższy
listing prezentuje ten skrypt opatrzony komentarzami wyjaśniającymi wykonywane działania:
Rysunek 6 - Katalogi.
16
#!/bin/sh
echo Subject: Konwersja baz eMarket
echo
echo Przygotowanie baz z MGZ do zasilenia bazy SQL eMarket....
# maska tworzenia plików z prawami odczyt/zapis dla wlasciciela i odczyt dla grupy
umask 027
# parametry poczatkowe, sciezki oraz zmienne:
KAT_MGZ='/work/sklep/MGZ2'
KAT_TMP='/bofh/bazy/emarket/tmp'
# sciezka do programu AnsiC konwersji dbf2lst
DBFLST='/bofh/bazy/emarket/bin/dbflst'
#echo Kasowanie starych zbiorow po poprzedniej konwersji
rm -f /bofh/bazy/emarket/tmp/*.LST
rm -f /bofh/bazy/emarket/tmp/*.IN
echo -n Start wstepnej konwersji zbiorkow zasilajacych glownych
$DBFLST $KAT_MGZ/CENNIK.DBF >$KAT_TMP/CENNIK.LST && echo -n .
$DBFLST $KAT_MGZ/CENY_DEW.DBF >$KAT_TMP/CENY_DEW.LST && echo -n .
$DBFLST $KAT_MGZ/ODBIORCY.DBF >$KAT_TMP/ODBIORCY.LST && echo -n .
$DBFLST $KAT_MGZ/DOSTAWCY.DBF >$KAT_TMP/DOSTAWCY.LST && echo .
echo -n Start konwersji zbiorkow stanow magazynowych
if [ -f $KAT_TMP/MAGAZYN.LST ] ; then
rm $KAT_TMP/MAGAZYN.LST
fi
# wykonujemy w petli przebieg po wszystkich katalogach magazynow
for MAGAZYN in `ls -d $KAT_MGZ/MAG_??` ; do
$DBFLST $MAGAZYN/MAGAZYN.DBF >>$KAT_TMP/MAGAZYN.LST && echo -n .
done
echo -n Wycinamy wymagane pola z rekordow
# i jak poszlo dobrze to kasujemy niepotrzebne *.LST
# konwersja pliku nazw towarow
cat $KAT_TMP/CENNIK.LST | awk -F\| 'ORS=""{}
{printf "%i", $2}
{gsub(" ", "", $3)} {print "|"toupper($3)}
{gsub(" ", "", $6)} {print "|"toupper($6)"|"}
{printf "%i", $7} {print "|"}
{printf "%G", $15} {print "\n"}' >$KAT_TMP/CENNIK.IN && \
rm $KAT_TMP/CENNIK.LST && echo -n .
# konwersja pliku cen towarow
cat $KAT_TMP/CENY_DEW.LST | awk -F\| 'ORS=""{}
{printf "%i",$2}{print "|"}
{printf "%G",$6}{print "|"}
{print $7"\n"}' >$KAT_TMP/CENY_DEW.IN && \
rm $KAT_TMP/CENY_DEW.LST && echo -n .
# konwersja pliku danych o dostawcach
cat $KAT_TMP/DOSTAWCY.LST | awk -F\| 'ORS=""{}
{printf "%i",$2}{print "|"}
{gsub(" ", "", $5)}{print toupper($5)"|"}
{gsub(" ", "", $6)}{print toupper($6)"|"}
{gsub(" ", "", $7)}{print toupper($7)"|"}
{gsub(" ", "", $8)}{print toupper($8)"|"}
{gsub(" ", "", $9)}{print toupper($9)"|"}
{gsub(" ", "", $10)}{print toupper($10)"|"}
{gsub(" ", "", $11)}{print toupper($11)"|"}
{gsub(" ", "", $12)}{print toupper($12)"|"}
{gsub(" ", "", $14)}{print $14"\n"}' >$KAT_TMP/DOSTAWCY.IN && \
rm $KAT_TMP/DOSTAWCY.LST && echo -n .
# konwersja pliku danych o odbiorcach
cat $KAT_TMP/ODBIORCY.LST | awk -F\| 'ORS=""{}
{printf "%i",$2}{print "|"}
{gsub(" ", "", $5)}{print toupper($5)"|"}
{gsub(" ", "", $6)}{print toupper($6)"|"}
{gsub(" ", "", $7)}{print toupper($7)"|"}
{gsub(" ", "", $8)}{print toupper($8)"|"}
{gsub(" ", "", $9)}{print toupper($9)"|"}
17
{gsub(" ", "", $14)}{print $14"\n"}' >$KAT_TMP/ODBIORCY.IN && \
rm $KAT_TMP/ODBIORCY.LST && echo -n .
# konwersja pliku cen towarow
cat $KAT_TMP/MAGAZYN.LST | awk -F\| 'ORS=""{}
{printf "%i",$2}{print "|"}
{gsub(" ", "", $6)}{print $6"|"}
{printf "%G",$14}{print "|"}
{printf "%G",$18}{print "\n"}' >$KAT_TMP/MAGAZYN.IN && \
rm $KAT_TMP/MAGAZYN.LST && echo -n .
# ustalamy prawa do plikow po konwersji
chmod u+rw-x,g+rw-x,o-rwx $KAT_TMP/CENNIK.IN
chmod u+rw-x,g+rw-x,o-rwx $KAT_TMP/CENY_DEW.IN
chmod u+rw-x,g+rw-x,o-rwx $KAT_TMP/MAGAZYN.IN
chmod u+rw-x,g+rw-x,o-rwx $KAT_TMP/DOSTAWCY.IN
chmod u+rw-x,g+rw-x,o-rwx $KAT_TMP/ODBIORCY.IN
echo
echo Zbiorki po konwersji zajmuja\:
echo
echo "KB: Nazwa:"
du -k $KAT_TMP/*.IN
echo
echo Upload plikow dla bazy emarket
cd /bofh/bazy/emarket/tmp
echo Zaczynamy transfer...
# wywolujemy skrypt ftp transfer na podstawie listy polecen
ftp -p < /bofh/bazy/emarket/tmp/transfer
echo Zaladowane.
ARCHIWIZACJA I PRZESYŁANIE DANYCH
Skrypty są wywoływane cyklicznie co noc wykonując wstępne przygotowanie danych oraz
ich przesłanie przy pomocy ftp na serwer internetowy, gdzie następuje dalsza faza obróbki
danych i ich ładowanie do PostgreSQL. Oprócz skasowania starych zbiorów i wkopiowania
na ich miejsce nowych wykonywana jest także archiwizacja. Przy pomocy poniższej listy
poleceń skryptu „transfer” dane przekazywane są protokołem ftp na serwer internetowy:
open inet-serwer
bin
cd /home/bazy/emarket/tmp
del /home/bazy/emarket/tmp/CENNIK.IN
del /home/bazy/emarket/tmp/CENY_DEW.IN
del /home/bazy/emarket/tmp/DOSTAWCY.IN
del /home/bazy/emarket/tmp/MAGAZYN.IN
del /home/bazy/emarket/tmp/ODBIORCY.IN
put /bofh/bazy/emarket/tmp/CENNIK.IN /home/bazy/emarket/tmp/CENNIK.IN
put /bofh/bazy/emarket/tmp/CENY_DEW.IN /home/bazy/emarket/tmp/CENY_DEW.IN
put /bofh/bazy/emarket/tmp/DOSTAWCY.IN /home/bazy/emarket/tmp/DOSTAWCY.IN
put /bofh/bazy/emarket/tmp/MAGAZYN.IN /home/bazy/emarket/tmp/MAGAZYN.IN
put /bofh/bazy/emarket/tmp/ODBIORCY.IN /home/bazy/emarket/tmp/ODBIORCY.IN
ls
bye
Wyniki wykonania całej operacji są „wyświetlane” na standardowe wyjście (stdout)
a następnie wysyłane pocztą email do administratora. Pozwala to na kontrolę poprawności
działania całej operacji, gdyż odbywa się ona bezobsługowo. Cykliczność wykonania tych
działań realizowana jest dwoma wpisami w pliku konfiguracyjnym crontab. Zapewniają one
18
oprócz konwersji i przekazania danych na serwer internetowy także archiwizację całości
systemów finansowo księgowych firmy i umieszczenie ich w katalogu udostępnionym
obsłudze w celu przegrania na wymienne nośniki:
# zrob archiwizacje codziennie po dniu roboczym o 3:00 w nocy
#minute hour mday month wday what
0 3 * * 1,2,3,4,5,6 /bofh/bin/archiwizacja 2>&1 | /usr/sbin/sendmail operator
# zrob konwersje i przekazanie danych o 20:00
0 20 * * 1,2,3,4,5,6 /bofh/bazy/emarket/konwersja 2>&1 | /usr/sbin/sendmail operator
Archiwizacja wykonywana jest poniższym skryptem przygotowującym paczkę do
przekopiowania na nośniki wymienne:
#!/bin/sh
# odpalane z crona codziennie w nocy
echo "Subject: Status archiwizacji"
echo
echo Archiwizacja...
dladaty=`date "+20%y%m%d"`
echo $dladaty
# przygotowanie ksiegowosci
echo KSH
cd /work/ksieg
tar -czf /home/archiwa/ksz$dladaty.tgz ./KSH2/*
echo OK.
# przygotowanie magazynow
echo MGZ
cd /work/sklep
tar -czf /home/archiwa/mzz$dladaty.tgz ./MGZ2/*
echo MGZ OK.
echo Archiwa zajmuja obecnie\:
echo KBytes Nazwa pliku
du -k /home/archiwa/
echo Zajetosc dyskow\:
df -ki
echo KONIEC
S
TRUKTURA KONWERTOWANYCH PLIKÓW
W celu wykonania operacji konwersji danych na potrzeby sklepu internetowego poddano
analizie zawartość poszczególnych plików DBF systemów magazynowych. Wyodrębniono
przydatne tabele i na ich podstawie określone pola niezbędne dla działania sklepu. Poniższy
fragment każdego z plików przekazywanych do zaimportowania do bazy SQL obrazuje jakie
dane są wykorzystane z systemów finansowo księgowych firmy:
# Fragment pliku CENNIK.IN
# struktura pliku:
# ind | nazwa towaru cp852 | ozn. producenta | stan | VAT
167636|UCHWYT ZEZ+OBEJMA FI 40|UCHWYT ZEZ+OBEJMA |16|22
982027|YAMAHA RX-V 520 AMPLITUNER AV TI.|YAMAHA RX-V520 TI.|17|22
300405|KAMERA PANASONIC NV-DS27 EG|PANASONIC NV-DS27 |57|22
300052|TELEWIZOR PANASONIC TX 29 AS10P|PANASONIC TX 29 AS|78|22
300051|MAGNETOWID PANASONIC NV-FJ 762 EE|PANASONIC NV-FJ762|78|22
300050|MAGNETOWID PANASONIC NV-FJ 622 EE|PANASONIC NV-FJ622|78|22
51102|PANASONIC RT-60MC MIKROKASETA|PANASONIC RT-60MC |5|22
19
# Fragment pliku CENY_DEW.IN
# struktura pliku:
# ind | cena netto | symbol waluty
330055|1146.72|ZLP
110003|548.36|ZLP
140021|1638.52|ZLP
167636|14.75|ZLP
500018|10.66|ZLP
51102|6.56|ZLP
# Fragment pliku DOSTAWCY.IN oraz ODBIORCY.IN
# struktura pliku:
# ind | nazwa krotka | nazwa dluga | adres1 ... adres6 | nip
1|WIZJA TV |WIZJA TV|UL.PAWINSKIEGO 5A/BLOK D|02-106 WARSZAWA |||||113-15-59-441
28|PHILIPS AGD|PHILIPS POLSKA SP Z.O.O.|02-222 WARSZAWA |AL. JEROZOLIMSKIE 195 B |||||526-021-09-55
43|THOMSON|THOMSON CONSUMER ELECTRONIC POLAND|UL OKULICKIEGO 7/9|05 - 500 PIASECZNO|||||123-00-05-409
# Fragment pliku MAGAZYN.IN
# struktura pliku:
# ind | data | stan dyspozycyjny | stan ksiegowy
240029|20010528|2|2
270018|20010703|28|28
270015|20010628|19|19
K
ONWERSJA STRONY KODOWEJ
Dane rejestrowane w systemie finansowo księgowym firmy są danymi „brudnymi” pod
względem historycznie stosowanych kodowań, poczynając od standardu polskich znaków
Mazovia poprzez Latin i inne a kończąc na cp852. Koniecznym było dokonanie konwersji
tych danych do jednolitego formatu jednak różnorodność stacji roboczych oraz ich
wykorzystanie do różnych celów wykluczała wykonanie tego na głównych danych firmy oraz
na zastosowanie jednakowego kodowania na wszystkich końcówkach. Konwersją polskich
znaków został „obarczony” serwer internetowy, gdzie wykonywana jest ona przed
wczytaniem danych do bazy SQL. Skrypt konwertujący jest uniwersalnym narzędziem
napisanym przez tragicznie zmarłego w 1996r dziennikarza Krakowskiej Gazety Wyborczej
Andrzeja Górbiela (
http://studweb.euv-frankfurt-o.de/twardoch/f/pl/comp/gorbiel/
Poniższy listing przedstawia tego skryptu:
#!/bin/sh
#
# plconv - filtr do konwersji polskich znakow diakrytycznych
#
# (c) 1994 Andrzej Gorbiel <A.Gorbiel@Ga-Wyb.Krakow.PL>
#
# a, c' e, l\ n' o' s' z' z. A, C' E, L\ N' O' S' Z' Z~
MAZ="\206\215\221\222\244\242\236\246\247\217\225\220\234\245\243\230\240\241"
LAT="\245\206\251\210\344\242\230\253\276\244\217\250\235\343\340\227\215\275"
WIN="\271\346\352\263\361\363\234\237\277\245\306\312\243\321\323\214\217\257"
ISO="\261\346\352\263\361\363\266\274\277\241\306\312\243\321\323\246\254\257"
TXT="acelnoszzACELNOSZZ"
MAZTAB="\206\215\221\222\244\242\236\246\247\217\225\220\234\245\243\230\240\241\277\263\
300\301\302\303\304\305\331\332\264"
ISOTAB="\261\346\352\263\361\363\266\274\277\241\306\312\243\321\323\246\254\257\53\174\53\
53\55\174\55\53\53\53\174"
ShowHelp ()
{
echo "Uzycie: $0 standard_we standard_wy [ < plik wejsciowy > plik wyjsciowy]"
20
echo " dopuszczalne standardy:"
echo " MAZ - Mazovia"
echo " LAT - Latin-2 (MS-DOS CP 852)"
echo " WIN - Windows ANSI (CP 1250)"
echo " ISO - ISO Latin-2 (CP 8859/2)"
echo " TXT - ASCII (7-bit)"
exit
}
SetStd ()
{
case $1 in
MAZ) std=$MAZ;;
LAT) std=$LAT;;
WIN) std=$WIN;;
ISO) std=$ISO;;
TXT) std=$TXT;;
MAZTAB) std=$MAZTAB;;
ISOTAB) std=$ISOTAB;;
*) ShowHelp; exit;;
esac
}
if [ $# != 2 ]; then ShowHelp; fi
SetStd $1
std1=$std
SetStd $2
tr "$std1" "$std"
Z
AŁADOWANIE DANYCH DO BAZY
P
OSTGRE
SQL
Pliki w formacie tekstowym wczytywane są po ich otrzymaniu z serwera wewnętrznego
firmy. W tym momencie wykonywana jest konwersja kodowania na stronę kodową iso88592
(obowiązujący standard kodowania polskich znaków na stronach www tzw. iso-latin2).
Proces ten wywoływany jest również jako cykliczne zadanie w crontab’ie następującym
wpisem w plik konfiguracyjny:
# zasilenie dla emarket codziennie o 20:30
30 20 * * 1,2,3,4,5,6,7 /home/bazy/emarket/zaladunek 2>&1 | /var/qmail/bin/qmail-inject
Skrypt „załadunek” zbudowany jest podobnie jak skrypt konwersji danych na maszynie
wewnętrznej i wykonywane działania wyświetla na standardowe wyjście (stdout) co jest
poprzez sposób wywołania w corntab wysyłane jako raport do operatora. Jedyną różnicą
w wywołaniu skryptu jest to, iż działa on na tej samej maszynie na której zainstalowany
został system pocztowy Qmail i dlatego można było wykorzystać jeden z pocztowych
programów usługowych do dostarczenia poczty bezpośrednio do adresata. Działania takie
maja na celu informowanie obsługi lub administratorów o wszelkich występujących
problemach a także o powodzeniu każdego z etapów działania sklepu internetowego.
Poniższy skrypt prezentuje działania wykonywane w czasie importowania danych do bazy
PostgreSQL:
21
#!/bin/sh
echo "From: zaladunek <operator@localhost>"
echo "To: operator"
echo "Subject: Zaladowanie danych do bazy eMarket."
echo
# zdefiniowanie polozenia katalogow roboczych
KAT_TMP='/home/bazy/emarket/tmp'
PATH=$PATH:/usr/local/pgsql/bin ; export PATH
BINARIA='/home/bazy/emarket'
cd $BINARIA
#echo Sprawdzenie czy sa sciagniete pliki zasilajace...
pliki=`ls $KAT_TMP/*.IN | wc -l`
if [ $pliki != 5 ] ; then
echo Brak pliku zasilajacego.
echo Sa tylko te...
echo
ls -l $KAT_TMP/*.IN
echo
echo STOP.
exit 1
fi
echo Jest wszystkie $pliki plikow...
echo
ls -l $KAT_TMP/*.IN
echo
# konwersja strony kodowej
echo Konwersja pl znakow na ISO
($BINARIA/bin/plznaki LAT ISO < $KAT_TMP/CENNIK.IN > $KAT_TMP/CENNIK.PL) && \
mv $KAT_TMP/CENNIK.PL $KAT_TMP/CENNIK.IN && chmod a+r $KAT_TMP/CENNIK.IN
# data i czas rozpoczecia zaladunku
echo Zaczynamy o `date`
# ustawienie znacznika zaladunku do bazy
# skrypty php sklepu blokuja dostep do bazy w przypadku
# wykrycia obecnosci tego pliku na czas zaladunku danych
echo Tymczasowe zablokowanie dostepu do bazy...
touch $KAT_TMP/emarket.tmp
# 90-usuntabele
$BINARIA/90-usuntabele
# 20-zaloztabele
$BINARIA/20-zaloztabele
# 30-zaladujtabele
$BINARIA/30-zaladujtabele
# 40-zalozindeksy
$BINARIA/40-zalozindeksy
# 40-zaladujkatalog
$BINARIA/40-zaladujkatalog
# 45-optymalizuj
$BINARIA/45-optymalizuj
# 50-statystyka
$BINARIA/50-statystyka
echo Konczymy o `date`
echo Odblokowanie dostepu do bazy...
rm $KAT_TMP/emarket.tmp
echo
echo Koniec ladowania.
22
Etapy załadunku danych do bazy zostały podzielone na osobne funkcjonalne części,
co ułatwia ewentualne diagnozowanie nieprawidłowości oraz dalszy rozwój tego
oprogramowania. Każdy ze skryptów wykonujących działania na bazie PostgreSQL otrzymał
nazwę zbudowaną z początkowych dwóch cyfr oznaczających jego logiczną kolejność
w wykonaniu, analogicznie jak to ma miejsce w przypadku skryptów startowych w systemach
UNIX typu SysV 4.2 gdzie takie uporządkowanie występuje oraz w systemach BSD
w skryptach administracyjnych (daily, weekly, monthly itp.). Omówienie poszczególnych
skryptów SQL zostało umieszczone w kolejnych rozdziałach traktujących o samej strukturze
bazy danych. Wspomnieć należy, iż aby wykorzystywać bazę danych PostgreSQL,
administrator bazy założył odpowiednich użytkowników bazy z hasłami dostępu
i uprawnieniami pozwalającymi na czynności administracyjne. Głównym plikiem
konfiguracyjnym PostgreSQL regulującym dostęp określonych użytkowników z określonych
hostów jest plik konfiguracyjny „pg_hba.conf”. Dokładnie omawia to dokumentacja
PostgreSQL (
www.postgresql.org/idocs/index.php?client-authentication.html
). Sposób
autoryzacji (czy system wymaga hasła czy też nie) jest definiowany w tym pliku. Na jego
końcu przy domyślnej konfiguracji znajdują się wpisy:
local all trust
host all 127.0.0.1 255.255.255.255 trust
Oznaczają one, że dla połączeń lokalnych (local) - czyli takich, gdzie łączymy się nie
korzystając z socket’ów tcpip i dla dowolnej bazy (all), system ma przyjąć regułę nie pytania
o hasło (trust) natomiast dla połączeń zdalnych, ale tylko z maszyny o adresie 127.0.0.1 (czyli
z tego samego hosta, ale przez sockety tcpip), będzie obowiązywać ta sama reguła. Aby to
zmienić należy zastąpić ostatnie słowo (trust) na "password" lub "crypt" (różnią się one
metodą przesyłania hasła). Przykładowo zapis:
host all 192.168.1.10 255.255.255.0 password
Oznacza, że osoby łączące się z serwera o adresie 192.168.1.10 oraz z całej klasy C (czyli
w rzeczywistości adres ip może być typu 192.168.1.*) muszą podać hasło aby dostać się do
dowolnej bazy. Chcąc wymusić aby wzorcowy szablon bazy PostgreSQL był także
chroniony można dokonać zapisu:
host template1 192.168.1.10 255.255.255.0 password
co oznacza, że te same osoby będą mogły teraz dostać się tylko do bazy template1 i będą
musiały podać hasło. Metod autoryzacji użytkowników jest wiele, między innymi także na
podstawie protokołu ident. Przy pisaniu reguł dostępu należy pamiętać o kolejności. Zawsze
użyta zostanie ta reguła która jest pierwsza w pliku i pasuje do sytuacji (regułki
przeszukiwane są w kolejności od początku do końca pliku aż do znalezienia pierwszej
23
pasującej i na tym się kończy przeszukiwanie). Poniższy zapis byłby błędny, pozbawiony
sensu poprzez swoją błędną hierarchię:
local all trust
host all 127.0.0.1 255.255.255.255 trust
local template1 password
host template1 127.0.0.1 255.255.255.255 password
Poprawnej konfigurację tej access listy przedstawia przykładowy zapis:
local template1 password
host template1 127.0.0.1 255.255.255.255 password
local all trust
host all 127.0.0.1 255.255.255.255 trust
W przypadku konfiguracji rzeczywistej sklepu wpisy dopuszczające ustalają dostęp do bazy
sklepu nazwanej jako „emarket” tylko i wyłącznie z serwera na którym działa sklep oraz dla
określonego użytkownika z hasłem. Zapewnia to już zwiększony poziom bezpieczeństwa
poprzez odrzucenie przez silnik bazy danych połączeń pochodzących zarówno od
użytkowników lokalnych serwera jak i zdalne próby nawiązania połączeń (choć to także
zabezpiecza zastosowany firewall). PostgreSQL zapewnia ponadto zabezpieczenia dostępu
do tabel zgodnie ze standardem SQL92 (
www.postgresql.org/idocs/index.php?sql.html
Na zasadzie przywilejów na konkretne działania i operacje na krotkach, tabelach, indeksach,
procedurach czy funkcjach. Można tu bardzo precyzyjnie określać co, kto jak i kiedy może
uczynić ze zgromadzonymi danymi, czy może tylko czytać nasze dane czy też usuwać,
poprawiać, dodawać nowe itp.
24
Rozdział IV – S
TRUKTU RA I ORGANIZACJA BAZY EMARKET
S
KRYPTY ADMINISTRACYJNE
Chcąc zautomatyzować czynności tworzenia i uruchomienia samej bazy, a także jej zasilania
danymi na potrzeby sklepu napisano skrypty powłoki wywołujące określone działania
w bazie danych. Proces zasilania danymi sterowany jest głównym skryptem wywołującym
poszczególne pod-skrypty odpowiedzialne za pewne fragmenty działania poszczególnych
zadań zasilania w dane całej bazy. Niektóre z nich uruchamiane są jednorazowo w momencie
tworzenia bazy lub też jej kasowania. Inne pozwalają na wykonanie pewnych analiz
(na obecnym etapie rozwoju aplikacji jedynie w minimalnym zakresie). Większość danych
ładowana jest bez natychmiastowego indeksowania co znacząco przyśpiesza całą operację.
Dopiero po załadowaniu danych do tabel następuje zaindeksowanie wszystkich wymaganych
pól w konkretnych tabelach. Na samym końcu wykonywana jest „odkurzenie” bazy
pozwalająca PostgreSQL’owi na empiryczną i statystyczną optymalizację wyszukiwania
danych. Poniższe zestawienie prezentuje i omawia wywołania wszystkich skryptów.
#!/bin/sh
# skrypt 20-zaloztabele
#
echo Zalozenie tabel bazy emarket SQL...
#
KAT_SQL='/home/bazy/emarket/sql'
SQL='/usr/local/pgsql/bin/psql -d emarket -q -f'
echo
echo cennik...
$SQL $KAT_SQL/create/create_table_cennik.sql
echo cenydew...
$SQL $KAT_SQL/create/create_table_cenydew.sql
echo dostawcy...
$SQL $KAT_SQL/create/create_table_dostawcy.sql
echo magazyn...
$SQL $KAT_SQL/create/create_table_magazyn.sql
echo odbiorcy...
$SQL $KAT_SQL/create/create_table_odbiorcy.sql
#echo informacje...
$SQL $KAT_SQL/create/create_table_informacje.sql
echo
echo Koniec.
Powyższy pod-skrypt „20-zaloztabele” odpowiedzialny jest za początkowe założenie
niezbędnych tabel w bazie SQL. Wywołuje już bezpośrednio, napisane w dialekcie języka
SQL92 polecenia zakładające poszczególne tabele z polami określonego typu.
25
#!/bin/sh
# skrypt 20-zaladujtabele
#
echo Zaladowanie danych do tabel bazy emarket SQL...
#
KAT_SQL='/home/bazy/emarket/sql'
SQL='/usr/local/pgsql/bin/psql -d emarket -q -f'
echo
echo cennik...
$SQL $KAT_SQL/load/load_table_cennik.sql
echo cenydew...
$SQL $KAT_SQL/load/load_table_cenydew.sql
#echo dostawcy...
#$SQL $KAT_SQL/load/load_table_dostawcy.sql
#echo odbiorcy...
#$SQL $KAT_SQL/load/load_table_odbiorcy.sql
echo magazyn...
$SQL $KAT_SQL/load/load_table_magazyn.sql
echo
echo Przy okazji skorygujemy bledne dane...
# pewne dane w zasilajacych zbiorach sa zduplikowane lub nie maja
# wypelnionych wymaganych pol (zaszlosc historyczna) poprawiamy to tutaj
SQL='/usr/local/pgsql/bin/psql -d emarket -q -c'
$SQL "UPDATE magazyn SET data='19940505' where data=' ';"
#$SQL "DELETE FROM cenydew where indeks='984001' and cenat='1036.07';"
SQL='/usr/local/pgsql/bin/psql -d emarket -q -f'
echo uniqmagazyn...
$SQL $KAT_SQL/load/load_table_uniqmagazyn.sql
echo Koniec.
Powyższy pod-skrypt „20-zaladujtabele” wywołuje skrypty SQL92 wczytujące i korygujące
dane bezpośrednio do tabel w bazie. Niektóre dane (np. odbiorcy i dostawcy) nie są
wczytywane na obecnym etapie rozwoju sklepu internetowego aż do momentu gdy aplikacja
zostanie rozbudowana o moduły współpracy z partnerami handlowymi (B2B), realizację
wymiany partnerskiej, dynamicznie konstruowane cenniki w zależności od podpisanych
umów na upusty globalne itp.
#!/bin/sh
# Skrypt 40-zaladujkatalog
#
echo Zaladowanie danych do tabel bazy emarket SQL...
#
KAT_SQL='/home/bazy/emarket/sql'
SQL='/usr/local/pgsql/bin/psql -d emarket -q -f'
echo
echo ladujemy dane do tabeli katalog...
$SQL $KAT_SQL/load/load_table_katalog.sql
echo indeksujemy tabele katalog...
$SQL $KAT_SQL/create/create_index_katalog.sql
echo
echo Koniec.
26
Powyższy pod-skrypt generuje dane do dodatkowo tworzonej tabeli zawierającej
wyfiltrowane dane z tabeli zawierającej dane ze wszystkich magazynów. W aplikacji
finansowo księgowej firmy, każdy magazyn zawiera towary współistniejące w innych
magazynach. Każdy z magazynów posiada ten sam indeks dla towaru, zgodny z innymi
indeksami w pozostałych magazynach. Każdy z nich ma swój własny stan magazynowy
(dyspozycyjny i księgowy) oraz cenę. Istnieje także globalna tabela cen dewizowych
przechowująca wspólną cenę dla wszystkich magazynów. Idea działania aplikacji
magazynowej w firmie jest taka, iż gdy istnieje dla danego towaru cena w centralnym cenniku
to właśnie ona ma przewagę nad ceną lokalną w danym magazynie, dlatego tutaj właśnie
zdecydowano się na zagregowanie tych danych w jeden wspólny rekord będący podstawą do
dalszego działania sklepu.
#!/bin/sh
# skrypt 40-zalozindeksy
echo Zalozenie indeksow bazy emarket SQL...
#
KAT_SQL='/home/bazy/emarket/sql'
SQL='/usr/local/pgsql/bin/psql -d emarket -q -f'
echo
echo cennik...
$SQL $KAT_SQL/create/create_index_cennik.sql
echo cenydew...
$SQL $KAT_SQL/create/create_index_cenydew.sql
#echo dostawcy...
#$SQL $KAT_SQL/create/create_index_dostawcy.sql
#echo magazyn...
#$SQL $KAT_SQL/create/create_index_magazyn.sql
#echo odbiorcy...
#$SQL $KAT_SQL/create/create_index_odbiorcy.sql
echo uniqmagazyn...
$SQL $KAT_SQL/create/create_index_uniqmagazyn.sql
echo informacje...
$SQL $KAT_SQL/create/create_index_informacje.sql
echo
echo Koniec.
Powyższy pod-skrypt indeksuje wszystkie potrzebne pola w tabelach. Wykonywane jest to
dopiero po wczytaniu całości danych gdyż w ten sposób uniknięto przebudowywania
indeksów przy wczytywaniu danych do tabel i uzyskano znaczny wzrost szybkości całej
operacji importu danych. Silnik bazy danych w takim przypadku nie wykonuje blokad przy
dodawaniu rekordów a i same dane są fizycznie na dysku nie pofragmentowane, co miało by
miejsce, gdyby równocześnie trwało przeplatanie wczytania rekordu i zaktualizowanie
indeksu. Co prawda leżący u podstaw systemu plików mechanizm dba o takie
27
rozmieszczenie plików, aby ulegały jak najmniejszej fragmentacji, jednak odbywa się to
kosztem zwiększonego zapotrzebowania na zasoby systemu. Pamiętajmy że PostgreSQL
przechowuje wszystkie obiekty bazy w osobnych plikach.
#!/bin/sh
# skrypt 45-optymalizuj
echo Optymalizacja bazy emarket SQL...
#
echo
/usr/local/pgsql/bin/vacuumdb --analyze emarket
echo
echo Koniec.
Powyższy pod-skrypt uruchamia mechanizmy analizy i optymalizacji wbudowane w silnik
bazy danych. W przypadku bazy, w której nie są kasowane tabele z danymi ten mechanizm
wpływa na „uczenie” się rozkładu danych i wykorzystywanie metod stochastycznych dla
przyspieszenia dostępu do danych i powinien być uruchamiany okresowo. W przypadku
bazy eMarket nie ma on aż tak znaczącego wpływu, gdyż dane są stale „młode”.
#!/bin/sh
# skrypt 50-statystyka
echo Statystyka tabel bazy emarket SQL...
#
KAT_SQL='/home/bazy/emarket/sql'
SQL='/usr/local/pgsql/bin/psql -d emarket -q -c'
echo
echo Zaladowane rekordy w tabelach:
echo cennik...... `$SQL "SELECT count(*) FROM cennik;"`
echo ceny_dew.... `$SQL "SELECT count(*) FROM cenydew;"`
echo dostawcy.... `$SQL "SELECT count(*) FROM dostawcy;"`
echo uniqmagazyn. `$SQL "SELECT count(*) FROM uniqmagazyn;"`
echo odbiorcy.... `$SQL "SELECT count(*) FROM odbiorcy;"`
echo katalog..... `$SQL "SELECT count(*) FROM katalog;"`
echo informacje.. `$SQL "SELECT count(*) FROM informacje;"`
echo
echo Koniec.
Powyższy pod-skrypt zlicza ilości krotek w poszczególnych tabelach po załadowaniu danych
oraz je prezentuje co zostaje dołączone przy wczytywaniu danych do raportu kontrolnego
przesyłanego administratorom.
#!/bin/sh
# skrypt 80-usunindeksy
echo Skasowanie indeksow bazy emarket SQL...
#
KAT_SQL='/home/bazy/emarket/sql'
SQL='/usr/local/pgsql/bin/psql -d emarket -q -f'
$SQL $KAT_SQL/delete/drop_all_index.sql
echo
echo Koniec.
28
#!/bin/sh
# skrypt 90-usuntabele
echo Skasowanie tabel bazy emarket SQL...
#
KAT_SQL='/home/bazy/emarket/sql'
SQL='/usr/local/pgsql/bin/psql -d emarket -q -f'
$SQL $KAT_SQL/delete/drop_all_table.sql
echo
echo Koniec.
Powyższe dwa pod-skrypty usuwają wszystkie tabele i indeksy z bazy eMarket. Wykonywane
są przed załadowaniem nowych danych do tabel. Operacja skasowania całych tabel wraz
z indeksami jest nieporównywalnie szybsza niż kasowanie zawartości tabel a ponadto
fizycznie usuwane są niepotrzebne już pliki z dysku serwera.
#!/bin/sh
# 95-czysctmp
echo Usuwanie plikow zasilenia bazy SQL eMarket....
#
# parametry poczatkowe:
KAT_TMP='/home/bazy/emarket/tmp'
rm $KAT_TMP/CENNIK.IN
rm $KAT_TMP/CENY_DEW.IN
rm $KAT_TMP/ODBIORCY.IN
rm $KAT_TMP/DOSTAWCY.IN
rm $KAT_TMP/MAGAZYN.IN
echo Zbiorki zasilajace usuniete!
echo Koniec.
Powyższy pod-skrypt ma na celu fizyczne usunięcie niepotrzebnych zbiorów zasilających.
Jest uruchamiany w tylko specyficznych przypadkach, bowiem zbiory te są kasowane przy
przesyłaniu danych z serwera wewnętrznego firmy.
#!/bin/sh
# 99-struktura
echo Struktura bazy emarket SQL...
#
echo "\d cennik"
| /usr/local/pgsql/bin/psql -d emarket 2>&1
echo "\d cenydew"
| /usr/local/pgsql/bin/psql -d emarket 2>&1
echo "\d uniqmagazyn" | /usr/local/pgsql/bin/psql -d emarket 2>&1
echo "\d dostawcy"
| /usr/local/pgsql/bin/psql -d emarket 2>&1
echo "\d odbiorcy"
| /usr/local/pgsql/bin/psql -d emarket 2>&1
echo "\d katalog"
| /usr/local/pgsql/bin/psql -d emarket 2>&1
echo "\d informacje"
| /usr/local/pgsql/bin/psql -d emarket 2>&1
echo Koniec.
Powyższy pod-skrypt ma za zadanie wyświetlenie struktury bazy danych eMarket. Służy
wyłącznie celom administracyjnym np. przy rozbudowywaniu i modyfikowaniu tabel, pól czy
29
indeksów. Wyniki jego wykonania posłużą do dalszej prezentacji struktur tabel bazy
w dalszej części pracy.
S
KRYPTY
SQL
Procedury operujące bezpośrednio w bazie danych zostały zapisane w postaci plików
skryptowych SQL92 wywoływanych „na żądanie”. Wykonują one czynności tworzenia,
usuwania, indeksowania danych przez silnik bazy danych. Automatyzują działania codziennej
obsługi bazy. Podzielone zostały tematycznie na kilka kategorii takich jak:
„create” – tworzenie tabel czy indeksów,
„delete” – kasowanie danych, tabel oraz indeksów,
„check” – kontrola i sprawdzanie bazy,
„load” – importowanie danych do tabel,
„analyse” – różnego rodzaju analizy danych i zestawienia.
K
ATEGORIA SKRYPTÓW
„
CREATE
”
Do tej kategorii zaliczają się skrypty tworzące tabele oraz indeksy:
create_table_odbiorcy.sql
create_index_odbiorcy.sql
create_table_dostawcy.sql
create_index_dostawcy.sql
create_table_magazyn.sql
create_index_magazyn.sql
create_table_informacje.sql
create_index_informacje.sql
create_table_cenydew.sql
create_index_cenydew.sql
create_table_cennik.sql
create_index_cennik.sql
dla tabel generowanych dynamicznie na podstawie danych:
create_index_uniqmagazyn.sql
create_index_katalog.sql
-- create_table_odbiorcy.sql
--
-- utworzenie tabeli [odbiorcy] zawierajacej kody i nazwy odbiorcow
--
CREATE TABLE odbiorcy (
kod
int4 NOT NULL,
nazwa
varchar(16),
nazwa1
varchar(41),
nazwa2
varchar(41),
nazwa3
varchar(41),
nazwa4
varchar(41),
nip
char(13)
);
-- COMMIT;
-- create_index_odbiorcy.sql
--
-- utworzenie wymaganych indeksow dla tabeli [odbiorcy]
--
CREATE INDEX odbiorcy_kod_idx ON odbiorcy (kod);
CREATE INDEX odbiorcy_nazwa_idx ON odbiorcy (nazwa);
CREATE INDEX odbiorcy_nazwa1_idx ON odbiorcy (nazwa1);
CREATE INDEX odbiorcy_nazwa2_idx ON odbiorcy (nazwa2);
-- COMMIT;
30
-- create_table_dostawcy.sql
--
-- utworzenie tabeli [dostawcy] zawierajacej kod, nazwy i rach. banku dostawcow
--
CREATE TABLE dostawcy (
kod
int4 NOT NULL,
nazwa
varchar(16),
nazwa1
varchar(41),
nazwa2
varchar(41),
nazwa3
varchar(41),
nazwa4
varchar(41),
bank1
varchar(41),
bank2
varchar(41),
bank3
varchar(41),
nip
char(13)
);
-- COMMIT;
-- create_index_dostawcy.sql
--
-- utworzenie wymaganych indeksow dla tabeli [dostawcy]
--
CREATE UNIQUE INDEX dostawcy_kod_idx ON dostawcy (kod);
CREATE INDEX dostawcy_nazwa_idx ON dostawcy (nazwa);
CREATE INDEX dostawcy_nazwa1_idx ON dostawcy (nazwa1);
CREATE INDEX dostawcy_nazwa2_idx ON dostawcy (nazwa2);
CREATE INDEX dostawcy_nip_idx ON dostawcy (nip);
-- COMMIT;
-- create_table_magazyn.sql
--
-- utworzenie tabeli [magazyn] zawierajacej indeksy, ceny i stany towarow
--
CREATE TABLE magazyn (
indeks
int4 not null,
data
char(8),
stan
decimal(9,2) DEFAULT '0.00',
standp
decimal(9,2) DEFAULT '0.00'
);
-- COMMIT;
-- create_index_magazyn.sql
--
-- utworzenie wymaganych indeksow dla tabeli [magazyn]
--
CREATE INDEX magazyn_indeks_idx ON magazyn (indeks);
-- COMMIT;
-- create_table_informacje.sql
--
-- utworzenie tabeli [informacje] zawierajacej indeksy i info o towarach
--
CREATE TABLE informacje (
indeks
int4 NOT NULL,
podtyp
int4 NOT NULL DEFAULT '0',
idpromocja
int4 NOT NULL DEFAULT '0',
pokaz
char(1) DEFAULT 'N',
skrot
varchar(200) DEFAULT '',
tytul
varchar(80) DEFAULT '',
opis
varchar(4000) DEFAULT '',
link1
varchar(80) DEFAULT '',
link2
varchar(80) DEFAULT '',
flagi
varchar(80) DEFAULT ''
);
-- COMMIT;
31
-- create_index_informacje.sql
--
-- utworzenie wymaganych indeksow dla tabeli [informacje]
--
CREATE INDEX informacje_indeks_idx ON informacje (indeks);
CREATE INDEX informacje_podtyp_idx ON informacje (podtyp);
CREATE INDEX informacje_idpromocja_idx ON informacje (idpromocja);
CREATE INDEX informacje_pokaz_idx ON informacje (pokaz);
-- COMMIT;
-- create_table_cenydew.sql
--
-- utworzenie tabeli [ceny_dew] zawierajacej indeksy i ceny(dewizowe) towarow
--
CREATE TABLE cenydew (
indeks
int4 NOT NULL,
cenar
decimal(9,2) DEFAULT '0.00',
wal
char(3) DEFAULT 'ZLP'
);
-- COMMIT;
-- create_index_cenydew.sql
--
-- utworzenie wymaganych indeksow dla tabeli [ceny_dew]
--
CREATE UNIQUE INDEX cenydew_indeks_idx ON cenydew (indeks);
-- COMMIT;
-- create_table_cennik.sql
--
-- utworzenie tabeli [cennik] zawierajacej indeksy i nazwy towarow
--
CREATE TABLE cennik (
indeks
int4 NOT NULL,
nazwa
varchar(34),
kod_prod
varchar(19),
dost
int4 NOT NULL,
vat
float4 DEFAULT '22'
);
-- COMMIT;
-- create_index_cennik.sql
--
-- utworzenie wymaganych indeksow dla tabeli [cennik]
--
CREATE UNIQUE INDEX cennik_indeks_idx on cennik (indeks);
CREATE INDEX cennik_nazwa_idx on cennik (nazwa);
CREATE INDEX cennik_kod_prod_idx on cennik (kod_prod);
CREATE INDEX cennik_dost_idx on cennik (dost);
-- COMMIT;
-- create_index_uniqmagazyn.sql
--
-- utworzenie wymaganych indeksow dla tabeli [uniqmagazyn]
--
CREATE UNIQUE INDEX uniqmagazyn_indeks_idx ON uniqmagazyn (indeks);
-- COMMIT;
-- create_index_katalog.sql
--
-- utworzenie wymaganych indeksow dla tabeli [katalog]
--
CREATE UNIQUE INDEX katalog_indeks_idx ON katalog (indeks);
CREATE INDEX katalog_nazwa_idx ON katalog (nazwa);
32
CREATE INDEX katalog_kodprod_idx ON katalog (kod_prod);
CREATE INDEX katalog_data_idx ON katalog (data);
-- COMMIT;
K
ATEGORIA SKRYPTÓW
„
DELETE
”
Do tej kategorii zaliczają się skrypty kasujące tabele oraz indeksy:
drop_all_table.sql
drop_all_index.sql
-- drop_all_table.sql
--
-- usuniecie wszystkich tabel
--
DROP TABLE cennik;
DROP TABLE cenydew;
DROP TABLE dostawcy;
DROP TABLE odbiorcy;
DROP TABLE magazyn;
DROP TABLE uniqmagazyn;
-- tabela informacje zostaje bo jest uzupelniana z zewnatrz
-- DROP TABLE informacje;
DROP TABLE katalog;
-- COMMIT;
-- drop_all_index.sql
--
-- usuniecie wszystkich indexow
--
DROP INDEX cennik_indeks_idx;
DROP INDEX cennik_nazwa_idx;
DROP INDEX cennik_kod_prod_idx;
DROP INDEX cennik_dost_idx;
DROP INDEX cenydew_indeks_idx;
DROP INDEX dostawcy_kod_idx;
DROP INDEX dostawcy_nazwa_idx;
DROP INDEX dostawcy_nazwa1_idx;
DROP INDEX dostawcy_nazwa2_idx;
DROP INDEX dostawcy_nip_idx;
DROP INDEX uniqmagazyn_indeks_idx;
DROP INDEX odbiorcy_kod_idx;
DROP INDEX odbiorcy_nazwa_idx;
DROP INDEX odbiorcy_nazwa1_idx;
DROP INDEX odbiorcy_nazwa2_idx;
-- DROP INDEX informacje_indeks_idx;
-- DROP INDEX informacje_podtyp_idx;
-- DROP INDEX informacje_idpromocja_idx;
-- DROP INDEX informacje_pokaz_idx;
DROP INDEX katalog_indeks_idx;
DROP INDEX katalog_nazwa_idx;
DROP INDEX katalog_kodprod_idx;
DROP INDEX katalog_data_idx;
-- COMMIT;
33
K
ATEGORIA SKRYPTÓW
„
LOAD
”
Do tej kategorii zaliczają się skrypty importujące dane do tabel oraz generujące dane na bazie
już istniejących poprzez agregację czy filtrowanie. Wczytywanie bazuje na specjalnej
metodzie importu danych bezpośrednio do tabeli w jednym przebiegu jaką udostępnia
backend PostgreSQL’a. Taki sposób importu definitywnie góruje nad metodami
tradycyjnymi, wykorzystującymi składnię „INSERT INTO”. Przewaga w szybkości jest
szczególnie widoczna przy „dużych” importach rzędu kilkadziesiąt tysięcy rekordów i więcej.
load_table_dostawcy.sql
load_table_odbiorcy.sql
load_table_cenydew.sql
load_table_uniqmagazyn.sql
load_table_cennik.sql
load_table_katalog.sql
load_table_magazyn.sql
-- load_table_dostawcy.sql
--
-- ladowanie danych do tabeli [dostawcy]
--
COPY dostawcy
FROM '/home/bazy/emarket/tmp/DOSTAWCY.IN'
USING DELIMITERS '|';
-- COMMIT;
-- load_table_odbiorcy.sql
--
-- ladowanie danych do tabeli [odbiorcy]
--
COPY odbiorcy
FROM '/home/bazy/emarket/tmp/ODBIORCY.IN'
USING DELIMITERS '|';
-- COMMIT;
-- load_table_cenydew.sql
--
-- ladowanie danych do tabeli [cenydew]
--
COPY cenydew
FROM '/home/bazy/emarket/tmp/CENY_DEW.IN'
USING DELIMITERS '|';
-- COMMIT;
-- load_table_uniqmagazyn.sql
--
-- Agregacja z tabeli magazyn do tabeli [uniqmagazyn] bazy emarket SQL...
--
SELECT
indeks AS agr_indeks,
max(data) AS ost_data,
sum(stan) AS suma_stan,
sum(standp) AS suma_standp
INTO TEMP agrmagazyn FROM magazyn GROUP BY indeks;
--
34
--
SELECT DISTINCT
agr_indeks,
ost_data,
suma_stan,
suma_standp
INTO TEMP tmpmagazyn FROM magazyn, agrmagazyn
WHERE agr_indeks=magazyn.indeks and ost_data=magazyn.data
ORDER BY agr_indeks;
--
--
--DROP TABLE uniqmagazyn;
SELECT
agr_indeks::int4 AS
indeks,
date(ost_data) AS
data,
suma_stan::float4
AS stan,
suma_standp::float4
AS standp
INTO uniqmagazyn FROM tmpmagazyn ORDER BY 1;
--
--
DROP TABLE magazyn;
-- load_table_cennik.sql
--
-- ladowanie danych do tabeli [cennik]
--
COPY cennik
FROM '/home/bazy/emarket/tmp/CENNIK.IN'
USING DELIMITERS '|';
-- COMMIT;
-- load_table_katalog.sql
--
-- zlozenie danych z tabel i zaladowanie do ostatecznej prezentacyjnej tabeli [katalog]
--
DROP TABLE katalog;
SELECT cennik.indeks, cennik.nazwa, cennik.kod_prod,
uniqmagazyn.data, cenydew.wal, cenydew.cenar AS netto,
--
numeric_round(cenydew.cenar::float8 * (cennik.vat /100+1),2) AS brutto,
round(cenydew.cenar::float8 * (cennik.vat /100+1),2) AS brutto,
standp AS nastanie
INTO
katalog
FROM cennik,uniqmagazyn,
cenydew
WHERE (cennik.indeks=uniqmagazyn.indeks)
AND
(cennik.indeks=cenydew.indeks)
--
AND standp>=0 AND
--
date_ge(uniqmagazyn.data,'1998-12-31') AND
--
(cennik.nazwa like '%%%%')
ORDER BY 3,2;
-- load_table_magazyn.sql
--
-- ladowanie danych do tabeli [cennik]
--
COPY magazyn
FROM '/home/bazy/emarket/tmp/MAGAZYN.IN'
USING DELIMITERS '|';
-- COMMIT;
35
K
ATEGORIA SKRYPTÓW
„
CHECK
”
Skrypty te wykonują kontrolę danych w tabelach ładowanych do bazy na obecność
zduplikowanych identycznych rekordów. W wyniku zawieszenia się komputera na którym
działa aplikacja finansowo księgowa indeksy NTX bazy danych DBF mogą zostać
uszkodzone, a wtedy może dojść do takiej sytuacji, że aplikacja „nieświadoma” dopisanej
pozycji do bazy na innej końcówce sieciowej może zarejestrować identyczną krotkę. Skrypty
te powstały gdy wynikła taka właśnie sytuacja. Następstwem tego była konieczność
„ręcznego”, usuwania błędnych rekordów z tabeli DBF oraz reindeksacja.
duplicated_cenydew.sql
duplicated_cennik.sql
duplicated_dostawcy.sql
duplicated_odbiorcy.sql
-- duplicated_cenydew.sql
--
-- Wyszukanie podwojnych indeksow w tabeli cenydew...
--
SELECT indeks, count(indeks) AS ile FROM cenydew
GROUP BY indeks HAVING count(indeks)>1;
-- duplicated_cennik.sql
--
-- Wyszukanie podwojnych indeksow w tabeli cennik...
--
SELECT indeks, count(indeks) AS ile FROM cennik
GROUP BY indeks HAVING count(indeks)>1;
-- duplicated_dostawcy.sql
--
-- Wyszukanie podwojnych identyfikatorow dostawcow...
--
SELECT kod, count(kod) AS ile FROM dostawcy
GROUP BY kod HAVING count(kod)>1;
-- duplicated_odbiorcy.sql
--
-- Wyszukanie podwojnych identyfikatorow odbiorcow...
--
SELECT kod, count(kod) AS ile FROM odbiorcy
GROUP BY kod HAVING count(kod)>1;
K
ATEGORIA SKRYPTÓW
„
ANALYSIS
”
W tej kategorii w pierwotnym zamierzeniu miały powstać skrypty wykonujące różne analizy
danych zebranych w tabelach, zarejestrowanych transakcjach i innych operacjach. Na
obecnym stadium rozwoju sklepu internetowego został opracowany jeden skrypt wybierający
towar „niechodliwy” z bazy danych. Skrypt występuje w dwóch wersjach a ich zadaniem jest
bardziej przetestowanie możliwości składania danych z wielu tabel niż użytkowe
wykorzystanie. W momencie gdy powstawał, dostępna wersja bazy PostgreSQL nie miała
36
jeszcze zaimplementowanej obsługi złączeń klauzulami „JOIN” i należy go traktować jako
„wprawkę” programistyczną, gdyż obecna wersja PostgreSQL zapewnia pełną obsługę
złączeń, lewo i prawostronnych a także zawężających itp.
-- niechodliwy.sql
--
-- Towar na stanie niechodliwy
--
SELECT *, float4(stan+standp)*float4(cenaz) AS wartosc
FROM uniqmagazyn,
cennik
WHERE uniqmagazyn.indeks=cennik.indeks
and
(stan + standp)>0 and
data<'1999-01-01'
ORDER BY data;
-- niechodliwy.sql
--
-- Towar na stanie niechodliwy zlaczenie bez klauzuli JOIN
--
SELECT uniqmagazyn.*, cennik.*
FROM uniqmagazyn,
cennik
WHERE uniqmagazyn.indeks=cennik.indeks
UNION ALL
SELECT uniqmagazyn.*, null, null, null, null, null
FROM uniqmagazyn
WHERE NOT EXISTS (
SELECT * FROM cennik
WHERE uniqmagazyn.indeks=cennik.indeks
);
S
TRUKTURA BAZY
Poniższe zestawienie jest wynikiem działania skryptu „99-struktura” generującego
schematyczną i poglądową prezentację struktury tabel i przynależnych im indeksów w bazie
danych eMarket. Listing ten prezentuje stan bazy po wykonaniu pełnego załadunku
wszystkich tabel oraz wykonaniu skryptów aktualizujących i czyszczących.
Table "cennik"
Attribute | Type | Modifier
-----------+-------------+--------------
indeks | integer | not null
nazwa | varchar(34) |
kod_prod | varchar(19) |
dost | integer | not null
vat | float4 | default '22'
Indices: cennik_dost_idx,
cennik_indeks_idx,
cennik_kod_prod_idx,
cennik_nazwa_idx
Table "cenydew"
Attribute | Type | Modifier
-----------+--------------+----------------
indeks | integer | not null
cenar | numeric(9,2) | default '0.00'
wal | char(3) | default 'ZLP'
Index: cenydew_indeks_idx
37
Table "uniqmagazyn"
Attribute | Type | Modifier
-----------+---------+----------
indeks | integer |
data | date |
stan | float4 |
standp | float4 |
Index: uniqmagazyn_indeks_idx
Table "dostawcy"
Attribute | Type | Modifier
-----------+-------------+----------
kod | integer | not null
nazwa | varchar(16) |
nazwa1 | varchar(41) |
nazwa2 | varchar(41) |
nazwa3 | varchar(41) |
nazwa4 | varchar(41) |
bank1 | varchar(41) |
bank2 | varchar(41) |
bank3 | varchar(41) |
nip | char(13) |
Indices: dostawcy_kod_idx,
dostawcy_nazwa1_idx,
dostawcy_nazwa2_idx,
dostawcy_nazwa_idx,
dostawcy_nip_idx
Table "odbiorcy"
Attribute | Type | Modifier
-----------+-------------+----------
kod | integer | not null
nazwa | varchar(16) |
nazwa1 | varchar(41) |
nazwa2 | varchar(41) |
nazwa3 | varchar(41) |
nazwa4 | varchar(41) |
nip | char(13) |
Indices: odbiorcy_kod_idx,
odbiorcy_nazwa1_idx,
odbiorcy_nazwa2_idx,
odbiorcy_nazwa_idx
Table "katalog"
Attribute | Type | Modifier
-----------+----------------------+----------
indeks | integer |
nazwa | varchar(34) |
kod_prod | varchar(19) |
data | date |
wal | char(3) |
netto | numeric(9,2) |
brutto | numeric(65535,65531) |
nastanie | float4 |
Indices: katalog_data_idx,
katalog_indeks_idx,
katalog_kodprod_idx,
katalog_nazwa_idx
Table "informacje"
Attribute | Type | Modifier
------------+---------------+----------------------
indeks | integer | not null
podtyp | integer | not null default '0'
idpromocja | integer | not null default '0'
pokaz | char(1) | default 'N'
skrot | varchar(200) | default ''
tytul | varchar(80) | default ''
opis | varchar(4000) | default ''
link1 | varchar(80) | default ''
link2 | varchar(80) | default ''
flagi | varchar(80) | default ''
Indices: informacje_idpromocja_idx,
informacje_indeks_idx,
informacje_podtyp_idx,
informacje_pokaz_idx
38
Rozdział V – W
YSZUKI WARKA TOWARÓW
K
ONCEPCJA DZIAŁANIA
Chcąc udostępnić potencjalnym klientom efektywny mechanizm wyszukiwania w bazie
towarów, koniecznym stało się skonstruowanie odpowiednich mechanizmów
umożliwiających pobranie od internauty interesującej go frazy, przeszukanie bazy danych
oraz zaprezentowanie znalezionych pozycji. Pewnym problemem wydaje się być tutaj
konieczność połączenia efektywności mechanizmu wyszukującego z możliwościami silnika
bazy danych. Większość obecnie istniejących baz danych nie pozwala na wyszukiwanie pełno
tekstowe przy wykorzystaniu indeksu, gdy szukana fraza zaczyna się znakiem wieloznacznym
w zapytaniu typu „LIKE”. Najnowsze komercyjne bazy danych takie jak ORACLE
wspierają ten mechanizm poprzez statyczne utworzenie specjalnych struktur indeksujących
pełnotekstowo całe pola tabeli, jednakże jedynie statycznie. Każdorazowe dopisanie krotki
do tabeli wymusza ponowne pełne zaindeksowanie całości tabeli, co praktycznie wyklucza to
zastosowanie w przypadku bazy, gdzie są modyfikowane lub dodawane kolejne rekordy.
Rysunek 7 – Główna strona wyszukiwarki towarów.
39
PostgreSQL posiada bardzo zaawansowane metody i typy indeksów, jednak nie ma
możliwości ich użycia w przypadku pełno tekstowego wyszukiwania. Silnik bazy danych jest
wtedy zmuszony do wykonania pełnego przejścia po wszystkich krotkach tabeli (tzw. full
scan), co drastycznie obniża efektywność tego rozwiązania.
W obecnej fazie rozwoju aplikacji sklepu internetowego zdecydowano się na pewne
rozwiązania skutecznie eliminujące wykonywanie pełnych przejść w tabelach. W głównym
oknie wyszukiwawczym jednak taki mechanizm musiał tymczasowo pozostać ze względu na
konieczność zapewnienia pełnego wyszukiwania nawet kosztem zwiększonego obciążenia
systemu i spowolnienia samego procesu. W module ofert przy przeglądaniu gotowych
zestawień towarów zastosowano zmodyfikowany mechanizm głównej wyszukiwarki w taki
sposób aby nie używać zapytań zaczynających się znakiem wieloznacznym „%” w klauzuli
„LIKE”, co spowodowało możliwość użycia przez motor bazy danych indeksów na
przeglądanych polach a tym samym wykonywanie całości zapytania w sposób błyskawiczny.
Chcąc rozwiązać tzw. problem „full text search” czyli pełnotekstowego wyszukiwania
korzystającego z indeksów (
http://www.postgresql.org/idocs/index.php?indices.html
w przygotowaniu jest pewien model przechowywania nazw towarów w dodatkowej tabeli,
ale w taki sposób, aby możliwym stało się zaindeksowanie każdego wyrazu z nazwy towaru.
Rysunek 8 – Prezentacja wyników wyszukania towaru.
40
Opracowana koncepcja polega na podziale nazwy towarów na odrębne wyrazy a następnie
na zapisanie ich do odrębnych pól w krotce.
41
Każde pole będzie polem indeksowanym tak więc przy konstruowaniu zapytania możliwym
stanie się wykorzystanie w mechanizmie wyszukiwarki odwołania klauzulami „LIKE” bez
podawania na początku znaku wieloznacznego „%” a zamianę szukanej frazy na kilka klauzul
typu „LIKE ‘towar%’” co pozwoli na użycie indeksów jak w przypadku przeszukania w
module ofert. Pole z nazwą towaru i oznaczeniem producenta sumarycznie ma długość
34+19=53 znaki. Zakładając że w skrajnym pesymistycznym przypadku nazwa towaru
złożona będzie z pojedynczych liter rozdzielanych spacjami to otrzymujemy około 27 pól
potrzebnych do rozbicia na wyrazy (53/2=26+1). Tak więc przyjmujemy, że 30 pól jest
liczbą wystarczająca w zupełności do przechowania całej nazwy towaru w rozbiciu na
wyrazy. Spacje nie są przechowywane bowiem służą jako znak, według którego dokonujemy
rozbicia na części składowe. Do kolejnych pól rekordu wpisujemy kolejne wyrazy z nazwy
towaru a sama treść zapytania wykonującego wyszukanie z wykorzystaniem indeksów
przyjmie wtedy przykładową postać:
SELECT * FROM subtowary WHERE pole1 LIKE ‘szukana_fraza%’ OR pole2 LIKE ‘szukana_fraza%’ OR .....;
Rysunek 9 – Prezentacja wyszukiwania predefiniowanego rodzaju towarów.
42
Zakładamy oczywiście że szukana fraza jest pojedynczym wyrazem, a w przypadku, gdy jest
to kilka wyrazów wykonujemy kilka takich zapytań dla każdego z wyrazów, składanych
następnie w jeden zestaw wyników. Powyższy projekt rozwiązania problemu pełnych przejść
po tabeli wydaje się być optymalnym do tego celu rozwiązaniem i będzie testowo
zastosowany w aplikacji sklepu internetowego w najbliższym czasie.
K
OD ŹRÓDŁOWY WYSZUKIWARKI
Mechanizm wyszukiwarki został wbudowany w różne strony serwisu internetowego firmy.
W momencie gdy powstawał, język PHP nie posiadał uniwersalnej klasy obiektów służących
do odwołania do bazy danych. Każdy typ bazy danych wymagał użycia innego zestawu
poleceń. Chcąc maksymalnie uniezależnić się od samej bazy zastosowano uniwersalne
definicje funkcji odwołujących się do baz. Wykorzystano do tego celu bibliotekę phpDB
której autorami są J. Thong i C. Fonk (
phpdb.linuxbox.com
) pozwalającą na zamaskowanie
metod odwołań do baz MySQL, MSQL, PostgreSQL, Microsoft SQL Server, Sybase na
jednakowe funkcje, co pozwala na łatwą wymianę silnika bazy na inny bez konieczności
zmian w kodzie źródłowym. Obecnie rozwijana jest przez autorów języka PHP, klasa
uniwersalnych funkcji, metod i obiektów pozwalająca na uniknięcie problemów związanych
z koniecznością przeróbek kodu w takich przypadkach. Projekt jest obecnie w bardzo
wczesnej fazie rozwoju jednakże fakt tworzenia go przez ekipę tworzącą PHP gwarantuje, że
wkrótce możliwym będzie zastosowanie go w systemach produkcyjnych. Nosi on nazwę
„PEAR: the PHP Extension and Application Repository”. Poniższy listing prezentuje i
omawia kod php wyszukiwarki. Niektóre linie zostały sztucznie podzielone na krótsze ze
względu na ich długość, co oznaczono znakiem „\” (jednakże on sam w kodzie oryginalnym
nie może występować). Listing zawiera komentarze świadczące o zespołowej pracy nad
powstającym kodem oraz podział zadań na kod dostępu i obsługi bazy danych oraz kod
dynamicznego generowania strony na podstawie pobieranych z bazy danych rekordów.
<?php
// obsluga bazy wyszukiwarki, plik: db.php3
// parametry glowne: uzytkownik, haslo, nazwa bazy itp.
include "inc/db.inc";
// zaimportowanie meta tagow
include "inc/meta.inc";
// phpDB wrapper do baz danych, wkoncu to ma chodzic na wszystkim :)
include "lib/phpDB.inc";
// data aktualizacji do ewentualnego wyswietlenia
// okreslana dynamicznie jako data i czas modyfikacji pliku index.php3
$datam = filemtime('index.php3');
$filemodtime = date("j m Y h:i:s A", $datam);
43
$aktual = strftime ('%d.%m.%Y godzina %H:%M:%S', $datam);
?>
<title>-x(AGD)x- .::EuroMarket - Video Tomex::. -x(RTV)x-</title>
<script language="JavaScript">
function WinOpen(file) {
window.open(file,'_blank','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes, \
resizable=no,width=600,height=400');}
</script>
</HEAD>
<body>
<? include "inc/menu.inc"; ?>
<br>
<div align="center">
<table width="760" cellpadding="0" cellspacing="0" border="0">
<tr><td width="200" align="center" valign="baseline">
<form action="db.php3" method="post">
<strong>Potrzebujesz czegoś?</strong><BR>
<input style="color: #1c36b3; background-color: #CCAC0C; font-size: 8pt; font-weight: 800;"
type="Text" size="14" name="towar" style="width: 140px">
<br><br><input style="background-color: #1c36b3; color: #CCAC0C; font-size: 8pt;
font-weight: 800;" type="Submit" value="Szukaj">
</form>
</td>
<td valign="top">
<strong><u>Możesz szukać korzystaj±c z symboli wieloznacznych:
</u></strong><br>
<table cellpadding="0" cellspacing="0" border="0">
<tr><td width="250" valign="bottom"><B>*</B> -zastępuje dowolny ciąg znaków<BR>
<B>?</B> -zastępuje jeden znak</td>
<td valign="bottom">Na przykład:<B>telewizor*philips</B>
<br> Bez względu na wielkość znaków.</td></tr></table>
<br>
Jeżeli chcesz zamówić wybrany produkt kliknij na ikonce <img src="gfx/wozek0.gif" alt="Zamow" border="0">
</td>
</tr>
</table>
</div>
<br>
<?php
// sprawdzamy czy akurat trwa ladowanie danych do bazy
// nie jest to najlepszy sposób, trzeba potem zmienic na lepszy
if ( ! file_exists("/home/bazy/tmp/emarket.tmp"))
{
//$towar z brany jest z tresci zapytania przegladarki trzeba go dobrze wyfiltrowac potem !!!
//trzeba oprogramowac wybor *? jak w dosie
//zastosowac regexpy do odfiltrowania niebezpiecznych znakow (sanity check)
/* tymczasowo wylaczam ten komunikat msg-box
if ($towar=="" or $towar==" "){
echo "<SCRIPT LANGUAGE=\"JavaScript\">\n";
echo "<!--\n";
echo "alert('Nie podales czego szukasz!')\n";
echo "history.back()\n";
echo "//-->\n";
echo "</SCRIPT>\n";
echo "<B>Nie podales czego szukasz!</B><BR>";
echo "<A HREF=\"$powrot\">Powrót</A>\n";
*/
}
//trzeba dorobic full_text_search do PostgreSQL bo inaczej nie posluguje sie tu indexami
//gdy w zapytaniu jest: like %costam% to jet robiony full table scan
// zduplikowanie zmiennej towar
$nazwa_tow = $towar;
//konwersja * i ? na % i _ dla SQL
$towar=strtr($towar,"*?","%_");
44
$towar="'%" . strtoupper($towar) ."%'";
// link powrotu
echo "<table width=\"760\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\">";
echo "<tr><td align=\"left\" valign=\"baseline\">";
echo "<div align=\"left\">";
echo "Wyniki wyszukiwania słowa "<b>" . $nazwa_tow . "</b>" :<br>";
echo "<B>Ceny detaliczne w zł, dla klientów indywidualnych.</B></div><br><br>";
echo "</td></tr></table>";
//echo "<FORM><INPUT TYPE=\"button\" VALUE=\"POWRÓT\"
onClick=\"parent.location.href='http://www.vt.pl'\"></FORM>";
?>
<!-- Tabela wynikow wyszukiwania-->
<?php
// jednorazowe stale i trwale polaczenie z baza
// presistent connections pozwalaja na urzymywanie przez Apache stalego polaczenia
// z PostgreSQL co wyjatkowo przyspiesza dzialanie calosci
$db=new phpDB();
$db->pconnect($db_host,$db_user,$db_pass,$db_name) or die("Wystapił błąd przy łączeniu!<BR>\n ");
// wersja zapytania po optymalizacji
$sqlquery="SELECT * FROM katalog WHERE nastanie>=0 AND date_ge(data, '$datapoczatkowa') ";
if ($towar=="'%%%'") { $selekcja="";}
else {$selekcja="AND ((nazwa like $towar) OR (kod_prod like $towar))";};
$sqlquery= $sqlquery .
$selekcja .
"LIMIT $ilosc OFFSET $start";
//echo $sqlquery . ";<BR><BR>\n";
$result = $db->execute($sqlquery) or die("Wystąpił błąd przy zapytaniu. <BR>\n");
$numrows=$result->getNumOfRows();
$numfields=$result->getNumOfFields();
?>
<TABLE WIDTH=760 BORDER=0 BGCOLOR="#b32f1c" cellpadding="0" cellspacing="0">
<TR>
<TD HEIGHT="14"><B style="color: #FFF200;">lp</B></TD>
<TD HEIGHT="14"><B style="color: #FFF200;">
zam.
nazwa towaru</B></TD>
<TD HEIGHT="14"><B style="color: #FFF200;">data ceny</B></TD>
<TD HEIGHT="14"><B style="color: #FFF200;">cena netto</B></TD>
<TD HEIGHT="14"><B style="color: #FFF200;">cena brutto</B></TD>
<TD HEIGHT="14"><B style="color: #FFF200;">dostępny</B></TD>
<TD HEIGHT="14"><B style="color: #FFF200;">info</B></TD>
</TR>
<?php
$row=0;
$img=0;
/*
Sprawdzamy czy przegladara ma jave czyli ze sie zglasza sie jako Mozilla
to jest naprawde proste i dobre i pewnie dodamy to do jakiego inc'luda na stale
fakt ze jak Mozilla to nie zawsze jest java ale to na razie wystarczy, a i links dziala :)
*/
if (strpos($HTTP_USER_AGENT, "ozilla") >0) {$jestjava=TRUE;} else {$jestjava=FALSE;};
/*
Poprawilem skladnie html'a bo sie rozlatywala zwlaszcza przy
generowaniu tabeli, teraz powinno byc ok w kazdej przegladarce.
Zmienilem troche wyglad stron, tak juz zostanie i pod ten styl bede wszystko zmienial
*/
// petla czytania kolejnych rekordow z bazy
while(!$result->EOF) {
// naprzemienne kolory wierszy
$bgcolor= ($row % 2) ;
if ($bgcolor<>0) { $bg=$bg1;} else { $bg=$bg2;};
$ind=$result->fields["indeks"];
45
// jesli przegladara zgodna z Mozilla to zakladamy ze ma jave
if ($jestjava){$wywolanie="JavaScript:WinOpen('towar.php?indeks=$ind')";} else
{$wywolanie="towar.php?indeks=$ind";}
if ($result->fields["nastanie"]==0) {$stan="-";} else {$stan="tak";};
if ($ind!=""){
// wyswieltamy naglowek i numer wiersza
echo "<TR BGCOLOR=$bg><TD ALIGN=\"center\">";
echo ($row + $start + 1);
echo "</TD>";
// podmiana obrazka do zamawiania - nie udalo mi sie zrobic ladnego przezroczystego gifa wiec PHP RuLeZ
if ($img == 0 ) {
echo "<TD> <a href=\"zakup.php3?towar=" . $result->fields["nazwa"] . "&netto=" . \
$result->fields["netto"] . "&brutto=" . $result->fields["brutto"] . "\"> \
<img name=\"wozek$row\" src=\"gfx/wozek00.gif\" alt=\"Zamawiam!\" border=\"0\"></a> \
<B> " . $result->fields["nazwa"]. " </b> </TD>";
$img = $img + 1;
}
else {
echo "<TD valign=\"middle\"> <a href=\"zakup.php3?towar=" . $result->fields["nazwa"] . \
"&netto=" . $result->fields["netto"] . "&brutto=" . $result->fields["brutto"] . "\"> \
<img name=\"wozek$row\" src=\"gfx/wozek0.gif\" alt=\"Zamawiam!\" border=\"0\"></a> \
<B> " . $result->fields["nazwa"]. " </b> </TD>";
$img = $img - 1;
}
echo "<TD><B> " .
$result->fields["data"]
. " </b></TD>";
echo "<TD ALIGN=\"right\"> " .
$result->fields["netto"]
. " </TD>";
echo "<TD ALIGN=\"right\"><B> ". $result->fields["brutto"]
. " </b></TD>";
echo "<TD ALIGN=\"center\"> ".
$stan
. " </TD>";
if ($stan == "tak") {
echo "<TD ALIGN=\"center\"> <a href=\"$wywolanie\">info</a> </TD>";}
else { echo "<TD ALIGN=\"center\"> $stan </TD>";};
echo "</TR>";
};
// pobieramy nastepna krotke
$result->nextRow();
$row++;
} // koniec petli czytania rekordow
//koniec tabeli
echo "</TABLE>\n";
/*
Te zagniezdzenia if trzeba poprzerabiac bo za duzo czasu tracimy na te warunki
narazie zostawie tak ale pozniej to pozmieniam, za to nie wyswietla pustego
wiersza tabeli z nr 1 oraz wyswietla info. Poprawnie zamyka tag tabeli.
Pewnie jeszcze zawine w to naglowek tabeli, pozniej...
*/
if ($ind=="") {
//nic nie znaleziono w bazie
echo "<b style=\"color:#DB2614\">";
echo "Przepraszamy!<BR>Nie znaleziono tego towaru w bazie.<BR>";
echo "Proszę spróbować ponownie oraz zmienić nazwę szukanego towaru...<BR><BR></b>";
};
// zwalniamy pamiec i zamykamy polaczenie z baza
// (teoretycznie bo przeciez uzywamy presistent connection ale na wszelki wypadek
// trzeba po sobie posprzatac
$result->close();
$db->close();
} //koniec warunku aktualizacji bazy
else { // wlasnie trwa aktualizacja sql'a
echo "<b style=\"color:#DB2614\">";
echo "Przepraszamy! <BR> Właśnie trwa aktualizacja bazy danych.<BR>";
echo "Proszę spróbować ponownie za chwilę...<BR><BR></b>";
};
onClick=\"parent.location.href='http://www.vt.pl'\"></FORM>";
echo "<br>";
include "inc/stopka.inc";
?>
</body></html>
46
Struktura powyższego skryptu wskazuje na wzajemne przeplatanie kodu PHP, JavaScript
oraz czystego HTML'u. Możliwe jest także wykonywanie wstawek w innych językach
skryptowych jak np. Visual Basic Script. Najczęściej powtarzające się fragmenty kodu zostały
wyodrębnione do zewnętrznych plików a następnie dołączane klauzulą „include” w różnych
podprogramach w celu predefiniowania i ujednolicenia wyglądu generowanych stron czy też
identycznego tworzenia meta-tagów itp. W całości generowanych stron kod opisu strony
napisany jest zgodnie z obowiązującym standardem HTML przy zastosowaniu stylów.
Ułatwia to zachowanie jednakowego wyglądu wszystkich generowanych stron. Poniższy
listing przedstawia główny plik definicji stylów.
body {
background-color : #DEBE09;
margin-top : 0px;
margin-right : 0px;
margin-left : 0px;
font-family : Verdana, Geneva, Arial, Helvetica, sans-serif;
font-size : 8pt;
color : #1e1164;
text-align: center;
}
.tab {
border-style: solid;
border-width: 1px;
border-top-width: 0px;
border-bottom-width: 0px;
}
a {
font-size: 8pt;
color : #0a0eb6;
font-family : Verdana, Geneva, Arial, Helvetica, sans-serif;
text-decoration : none;
}
a:hover {
font-size: 8pt;
color : red;
font-family : Verdana, Geneva, Arial, Helvetica, sans-serif;
text-decoration : none;
}
td. {
font-family : Arial, Helvetica, sans-serif;
font-size : 8pt;
color : #1e1164;
font-family : Verdana, Geneva, Arial, Helvetica, sans-serif;
}
.m7 {
font-size: 7px;
color : #eac137;
font-weight : 800;
font-family : Verdana, Geneva, Arial, Helvetica, sans-serif;
text-decoration : none;
}
.m {
font-size: 10px;
color : SlateGray;
font-weight : 800;
font-family : Verdana, Geneva, Arial, Helvetica, sans-serif;
}
47
.cena {
border-style: solid;
border-width: 1px;
border-color: black;
border-left-widht: 1px;
border-right-widht: 1px;
border-top-widht: 1px;
border-bottom-widht: 1px;
text-align: center;
font-size: 15px;
color : Yellow;
background-color: #b32f1c;
font-weight : 900;
font-family : Verdana, Geneva, Arial, Helvetica, sans-serif;
}
.cena2 {
border-style: solid;
border-width: 1px;
border-color: black;
border-left-widht: 1px;
border-right-widht: 0px;
border-top-widht: 1px;
border-bottom-widht: 1px;
text-align: center;
font-size: 15px;
color : black;
background-color: #DEBE09;
font-weight : 100;
font-family : Verdana, Geneva, Arial, Helvetica, sans-serif;
}
.cena3 {
border-style: solid;
border-width: 1px;
border-color: black;
border-left-widht: 0px;
border-right-widht: 1px;
border-top-widht: 1px;
border-bottom-widht: 1px;
text-align: center;
font-size: 15px;
color : Yellow;
background-color: navy;
font-weight : 900;
font-family : Verdana, Geneva, Arial, Helvetica, sans-serif;
}
.red {
color: #ac0f0f;
}
Zastosowanie stylów pozwala na szybkie zmiany wyglądu prezentowanych stron
w przypadku „odświeżania” serwisu internetowego za pomocą zmiany projektu graficznego.
Plik z opisem styli jest przez przeglądarkę wczytywany po napotkaniu odwołania w treści
strony do dokumentu z definicjami używanych styli. Poniższy listing prezentuje główny plik
generujący większość stron serwisu.
<? include "inc/meta.inc"; ?>
<title>-x(AGD)x- .::EuroMarket - Video Tomex::. -x(RTV)x-</title>
<script language="JavaScript">
function WinOpen(file) {
window.open(file,'_blank','toolbar=no,location=no,directories=no,status=no,menubar=no, \
scrollbars=no,resizable=no,width=300,height=240');}
</script>
<script language="JavaScript"><!--
function chBr(br, ver) {
if (!(chBr.arguments.length > 0)) return (navigator.appName + ', ' + navigator.appVersion);
var Browser = br;
var Version = (chBr.arguments.length > 1) ? ver : null;
var BrowserFlag = (navigator.appName.toLowerCase() == Browser.toLowerCase()) ? true : false;
48
if (Version != null) {
var VersionFlag = (navigator.appVersion.substring(0,1) == Version.substring(0,1)) ? true : false;
}
return (BrowserFlag || VersionFlag);
}
function openr(dokument)
{
window.open(dokument,'parent[oferta]');
}
// --></script>
</head>
<body bgcolor="#DEBE09">
<br>
<?
# jezeli brak zapytania startuj tutaj
if ($ID == '') {
?>
<div align="center">
<table cellpadding="0" cellspacing="0" border="0">
<tr><td width="150" valign="top">
<? include "inc/targi.inc" ?>
</TD><td valign="top">
<!— Generowanie przyciskow metoda rollover -->
<!-- <div align="right"><a class="fade" href="javascript:WinOpen('inc/s_help.html')">pomoc</a></div> -->
</div>
<table cellpadding="0" cellspacing="0" border="0">
<tr><td valign="top">
<div align="center">
<a href="?ID=05"><img src="gfx/logo_vt.gif" width="361" height="107" alt="" border="0"></a><br>
</div>
<a href="?ID=01" onmouseover="if ( (chBr('Netscape', '3') == true) || (chBr('Explorer', '4') == true) )
menu1.src='gfx/onas_1.gif'"
onmouseout="if ( (chBr('Netscape', '3') == true) || (chBr('Explorer', '4') == true) )
menu1.src='gfx/onas_0.gif'">
<img name="menu1" src="gfx/onas_0.gif" alt="" border="0"></a></td></tr>
<tr><td width="94" align="right">
<a href="?ID=02" onmouseover="if ( (chBr('Netscape', '3') == true) || (chBr('Explorer', '4') == true) )
menu2.src='gfx/szukaj_1.gif'"
onmouseout="if ( (chBr('Netscape', '3') == true) || (chBr('Explorer', '4') == true) )
menu2.src='gfx/szukaj_0.gif'">
<img name="menu2" src="gfx/szukaj_0.gif" alt="" border="0"></a></td></tr>
<tr><td width="108" align="right">
<a href="http://poczta.vt.pl" onmouseover="if ( (chBr('Netscape', '3') == true) || (chBr('Explorer', '4')
== true) ) menu3.src='gfx/internet_1.gif'"
onmouseout="if ( (chBr('Netscape', '3') == true) || (chBr('Explorer', '4') == true) )
menu3.src='gfx/internet_0.gif'">
<img name="menu3" src="gfx/internet_0.gif" alt="" border="0"></a></td></tr>
<tr><td width="129" align="right">
<a href="?ID=04" onmouseover="if ( (chBr('Netscape', '3') == true) || (chBr('Explorer', '4') == true) )
menu4.src='gfx/partnerzy_1.gif'"
onmouseout="if ( (chBr('Netscape', '3') == true) || (chBr('Explorer', '4') == true) )
menu4.src='gfx/partnerzy_0.gif'">
<img name="menu4" src="gfx/partnerzy_0.gif" alt="" border="0"></a></td></tr>
<tr><td width="161" align="right">
<a href="?ID=05" onmouseover="if ( (chBr('Netscape', '3') == true) || (chBr('Explorer', '4') == true) )
menu5.src='gfx/promocje_1.gif'"
onmouseout="if ( (chBr('Netscape', '3') == true) || (chBr('Explorer', '4') == true) )
menu5.src='gfx/promocje_0.gif'">
<img name="menu5" src="gfx/promocje_0.gif" alt="" border="0"></a></td></tr>
<tr><td width="221" align="right">
<a href="oferta.php3" onmouseover="if ( (chBr('Netscape', '3') == true) || (chBr('Explorer', '4') ==
true) ) menu6.src='gfx/oferta_1.gif'"
onmouseout="if ( (chBr('Netscape', '3') == true) || (chBr('Explorer', '4') == true) )
menu6.src='gfx/oferta_0.gif'">
<img name="menu6" src="gfx/oferta_0.gif" alt="" border="0"></a></td></tr>
<tr><td width="290" align="right">
<a href="index.php3?ID=07" onmouseover="if ( (chBr('Netscape', '3') == true) || (chBr('Explorer', '4') ==
true) ) menu7.src='gfx/pkth_1.gif'"
onmouseout="if ( (chBr('Netscape', '3') == true) || (chBr('Explorer', '4') == true) )
menu7.src='gfx/pkth_0.gif'">
<img name="menu7" src="gfx/pkth_0.gif" alt="" border="0"></a></td></tr>
</table>
49
<div align="center">
<!— wywolanie wyszukiwania -->
<form action="db.php3" method="post">
<b>Szukasz czegoś???</b><br>
<input style="color: #1c36b3; background-color: #CCAC0C; font-size: 8pt; font-weight: 800;"
type="Text" size="14" name="towar" style="width: 140px">
<input style="background-color: #1c36b3; color: #CCAC0C; font-size: 8pt; font-weight: 800;"
type="Submit" value="Szukaj">
</form>
</div>
</td>
<td valign="top">
<!— Dodatkowe elementy promocyjne -->
<? include "inc/targi2.inc"; ?>
</td>
</tr>
</table>
</div>
<br>
<?
}
# tutaj jezeli wyslane jest zapytanie inne niz null
else {
include "inc/menu.inc";
?>
<br>
<table cellpadding="0" cellspacing="0" border="0" width="750">
<tr>
<td align="center">
<?
include "inc/$ID.inc";
?>
</td></tr>
</table>
<? include "inc/stopka.inc"; ?>
<?
}
?>
</td></tr>
</table>
<br>
</body>
</html>
Skrypt ten jest wywoływany jako strona główna i od niego zaczyna działanie całość
wyszukiwania oraz nawigowania po stronach serwisu. Przy konstruowaniu serwisu
zbudowano moduł „oferta” generujący w predefiniowany sposób konkretne pozycje
tematyczne posiadanych towarów. Poniższy kod jest odpowiedzialny za obsługę
i wyświetlanie tych informacji.
50
<?php
// obsluga bazy wyszukiwarki
// parametry glowne
include "inc/db.inc";
// licznik :>
include "inc/counter.inc";
// meta
include "inc/meta.inc";
// phpDB wrapper do baz danych, wkoncu to ma chodzic na wszystkim :)
include "lib/phpDB.inc";
// aktualizacja
$datam = filemtime('index.php3');
$filemodtime = date("j m Y h:i:s A", $datam);
$aktual = strftime ('%d.%m.%Y godzina %H:%M:%S', $datam);
?>
<script language="JavaScript"><!--
function chBr(br, ver) {
if (!(chBr.arguments.length > 0)) return (navigator.appName + ', ' + navigator.appVersion);
var Browser = br;
var Version = (chBr.arguments.length > 1) ? ver : null;
var BrowserFlag = (navigator.appName.toLowerCase() == Browser.toLowerCase()) ? true : false;
if (Version != null) {
var VersionFlag = (navigator.appVersion.substring(0,1) == Version.substring(0,1)) ? true : false;
}
return (BrowserFlag || VersionFlag);
}
function openr(dokument)
{
window.open(dokument,'parent[oferta]');
}
// --></script>
<title>-x(AGD)x- .::EuroMarket - Video Tomex::. -x(RTV)x-</title>
<script language="JavaScript">
function WinOpen(file) {
window.open(file,'_blank','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resi
zable=no,width=600,height=400');}
</script>
</HEAD>
<body>
<? include "inc/menu.inc"; ?>
<br>
<div align="center">
<table width="760" cellpadding="0" cellspacing="0" border="0">
<tr>
<td align="center" width="150" valign="top">
<table border="0" width="150" cellpadding="0" cellspacing="0">
<tr>
<tr><td align="center" height="16" style="background-color: #b32f1c; color: yellow"><strong>Nasza
Oferta</strong></td></tr>
</tr>
</table>
<br>
<table border="0">
<tr>
<td align="center">
<a style="font-size: 7pt" href="oferta.php3?show=telewizory">
<img src="gfx/tv.gif" alt="" border="0"><br>
telewizory
</a>
</td>
</tr>
<tr>
<td align="center">
<a style="font-size: 7pt" href="oferta.php3?show=magnetofony">
51
<img src="gfx/mag.gif" alt="" border="0"><br>
magnetofony, radiomagnetofony
</a>
</td>
</tr>
<tr>
<td align="center">
<a style="font-size: 7pt" href="oferta.php3?show=pralki">
<img src="gfx/pralka.gif" alt="" border="0"><br>
pralki, zmywarki
</a>
</td>
</tr>
<tr>
<td align="center">
<a style="font-size: 7pt" href="oferta.php3?show=lodowki">
<img src="gfx/lodowka.gif" alt="" border="0"><br>
lodówki, zamrażarki
</a>
</td>
</tr>
<tr>
<td align="center">
<a style="font-size: 7pt" href="oferta.php3?show=vhsdvd">
<img src="gfx/magnetowid.gif" alt="" border="0"><br>
magnetowidy, odtwarzacze CD/DVD
</a>
</td>
</tr>
<tr>
<td align="center">
<a style="font-size: 7pt" href="oferta.php3?show=kuchnie">
<img src="gfx/kuchnia.gif" alt="" border="0"><br>
kuchnie
</a>
</td>
</tr>
<tr>
<td align="center">
<a style="font-size: 7pt" href="oferta.php3?show=odkurzacze">
<br>
odkurzacze
</a>
</td>
</tr>
</table>
<br>
<br>
</td>
<td width="610" valign="top" align="center">
<div align="center">
<table width="600" bgcolor="#e2b412" cellpadding="0" cellspacing="0" border="0">
<tr><td width="200" align="center" valign="baseline">
<form action="db.php3" method="post">
<strong>Potrzebujesz czego¶?</strong><BR>
<input style="color: #1c36b3; background-color: #CCAC0C; font-size: 8pt; font-weight: 800;"
type="Text" size="14" name="towar" style="width: 140px">
<br><br><input style="background-color: #1c36b3; color: #CCAC0C; font-size: 8pt;
font-weight: 800;" type="Submit" value="Szukaj">
</form>
</td>
<td valign="top">
Możesz szukać korzystając z symboli wieloznacznych:<br><br>
<table cellpadding="0" cellspacing="0" border="0">
<tr><td width="250" valign="bottom">
<B>*</B> -zastępuje dowolny ciąg znaków<BR>
<B>?</B> -zastępuje jeden znak</td>
<td valign="bottom">Na przykład:<B>telewizor*philips</B>
<br> Bez względu na wielkość znaków.</td></tr></table>
</td>
</tr>
</table>
</div>
52
<br>
<?
// wyswietlamy konkretna _jedno_ lub _dwuwyrazowa_ kategorie
// np. $pokaz='pralka ariston'
if ($show == '') {
include "inc/oferta.inc";
}
else {
if ($show == 'pralki' ) {
include "inc/pralki.inc";
}
if ($show == 'telewizory' ) {
include "inc/tv.inc";
}
if ($show == 'lodowki' ) {
include "inc/lodowki.inc";
}
if ($show == 'magnetofony' ) {
include "inc/magnetofon.inc";
}
if ($show == 'vhsdvd' ) {
include "inc/vhsdvd.inc";
}
if ($show == 'kuchnie' ) {
include "inc/kuchnie.inc";
}
if ($show == 'odkurzacze' ) {
include "inc/odkurzacze.inc";
}
}
?>
<br>
<br>
<?
include "inc/stopka.inc";
?>
</td></tr></table>
</body></html>
Podsumowaniem całości kodu jest fragment obsługujący przyjęcie i zrealizowanie
zamówienia. W obecnym stadium rozwoju sklepu jest to realizowane poprzez wysłanie
przyjętych informacji pocztą email do obsługującego pracownika. Trwają prace nad
skonstruowaniem wygodnego i sprawnego w działaniu „koszyka” obsługującego komplet
czynności związanych z realizacją zamówień. Obecnie wykorzystywany formularz także jest
generowany dynamicznie przez skrypt napisany w PHP. Dane wprowadzane do formularza
są sprawdzane. Testowane jest wypełnienie wymaganych pól, niezbędnych do podjęcia
dalszych działań związanych z realizacja zamówienia.
53
54
Poniższy rysunek przedstawia proces wprowadzania danych na stronie formularza, tuż po
wybraniu odnośnika ukrytego pod postacią ikony koszyka sklepowego.
Całość realizowanych działań związanych ze złożeniem zamówienia wykonuje poniższy kod.
<? include "inc/meta.inc"; ?>
<title>-x(AGD)x- .::EuroMarket - Video Tomex::. -x(RTV)x-</title>
<script language="JavaScript">
function WinOpen(file) {
window.open(file,'_blank','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resiz
able=no,width=300,height=240');
}
</script>
<script language="JavaScript"><!--
function chBr(br, ver) {
if (!(chBr.arguments.length > 0)) return (navigator.appName + ', ' + navigator.appVersion);
var Browser = br;
var Version = (chBr.arguments.length > 1) ? ver : null;
var BrowserFlag = (navigator.appName.toLowerCase() == Browser.toLowerCase()) ? true : false;
if (Version != null) {
var VersionFlag = (navigator.appVersion.substring(0,1) == Version.substring(0,1)) ? true : false;
}
return (BrowserFlag || VersionFlag);
}
function openr(dokument)
{
Rysunek 10 – Formularz realizacji zamówienia.
55
window.open(dokument,'parent[oferta]');
}
// --></script>
</head>
<body>
<? include "inc/menu.inc"; ?>
<br>
<div align="center">
<?
if ( $wysylaj == 'tak' ) {
$zamowienie = $towar + "\n" + $imie;
$czas = date(H) . ":" . date(i) . ":" . date(s);
mail("sklep@vt.pl", "Zamowienie - $towar", "Zamówienia dokonano z adresu:
$REMOTE_ADDR -- godzina $czas\n\n
Nowe zamowienie internetowe!\n\n
Nazwa towaru: $towar\n
Ilość: $ilosc\n
Cena brutto: $brutto\n
Imię: $imie\n
Nazwisko: $nazwisko\n
Telefon: $telefon\n
Ulica: $ulica\n
Miejscowość: $miejscowosc\n
Województwo: $wojewodztwo\n
Dochody: $dochody zł\n\n
Dane firmy/instytucji:\n\n
Nazwa firmy/instytucji: $nazwa_firmy\n
NIP: $nip\n
Regon: $regon\n\n
Zamówienie należy zrealizować w przeci±gu 24 godzin! :>" , "From: emarket@localhost");
?>
<br><br><br>
<strong style="font-size: 15pt; font-weight: 900; color: ">Dziękujemy!!!</strong><br><br><br>
<strong>Zamówienie zostało wysłane, w ciągu 24 godzin skontaktujemy się w celu potwierdzenia
zakupu.</strong>
<br><br><br>
<strong><a style="font-size:11pt" class="fade"
href="http://vt.pl/index.php3?ID=05">Powrót do oferty</a></strong>
<?
}
else {
if ( $check == '' ) {
?>
<table width="550" cellpadding="0" cellspacing="0" border="0">
<tr>
<td align="center">
<strong style="font-size:14pt">Zamówienie: <strong style="color:#b32f1c">
<? echo $towar ?></strong></strong><br>
<strong style="font-size:11pt">Cena netto: <strong style="color:#b32f1c">
<? echo $netto ?></strong></strong> <strong style="font-size:11pt">
Cena brutto: <strong style="color:#b32f1c"><? echo $brutto ?></strong></strong>
<br><br>
Przed wysłaniem zamówienia należy wypełnić poniższy formularz. Po wysłaniu zamówienia w ci±gu 24 godzin
skontaktujemy się z Państwem w celu potwierdzenia zamówienia.
Pola, przy których znajduje się <strong style="color:#b32f1c">*</strong>
są niezbędne i należy je wypełnić.<br>
<br>
<form action="zakup.php3?check=confirm" method="post">
<table cellpadding="4" cellspacing="4" border="0">
<tr>
<td align="right"><strong>Towar<strong style="color:#b32f1c">*</strong></strong></td>
<td align="left"><input disabled maxlength="40" type="Text" style="width:160"
name="towar" value="<? echo $towar ?>"> </td>
</tr>
<tr>
<td align="right"><strong>Ilo¶ć (szt)<strong style="color:#b32f1c">*</strong></strong></td>
<td align="left"><input maxlength="3" value="1" type="Text" style="width:30" name="ilosc"> </td>
</tr>
<tr>
<td align="right"><strong>Imię<strong style="color:#b32f1c">*</strong></strong></td>
<td align="left"><input maxlength="40" type="Text" style="width:160" name="imie"> </td>
</tr>
56
<tr>
<td align="right"><strong>Nazwisko<strong style="color:#b32f1c">*</strong></strong></td>
<td align="left"><input maxlength="40" type="Text" style="width:160" name="nazwisko"> </td>
</tr>
<tr>
<td align="right"><strong>Ulica/nr domu<strong style="color:#b32f1c">*</strong></strong></td>
<td align="left"><input maxlength="40" type="Text" style="width:160" name="ulica"> </td>
</tr>
<tr>
<td align="right"><strong>Miejscowo¶ć<strong style="color:#b32f1c">*</strong></strong></td>
<td align="left"><input maxlength="40" type="Text" style="width:160" name="miejscowosc"> </td>
</tr>
<tr>
<td align="right"><strong>Województwo</strong><strong style="color:#b32f1c">*</strong></td>
<td align="left">
<select name="wojewodztwo" id="wojewodztwo" style="width: 150">
<option value="none">wybierz</option>
<option value="zagranica">- zagranica -</option>
<option value="doloslaskie">dolnośląskie</option>
<option value="kujawsko-pomorskie">kujawsko-pomorskie</option>
<option value="lubelskie">lubelskie</option>
<option value="lubuskie">lubuskie</option>
<option value="lodzkie">łódzkie</option>
<option value="malopolskie">małopolskie</option>
<option value="mazowieckie">mazowieckie</option>
<option value="opolskie">opolskie</option>
<option value="podkarpackie">podkarpackie</option>
<option value="podlaskie">podlaskie</option>
<option value="pomorskie">pomorskie</option>
<option value="slaskie">śląskie</option>
<option value="swietokrzyskie">świętokrzyskie</option>
<option value="warmińsko-mazurskie">warmińsko-mazurskie</option>
<option value="wielkopolskie">wielkopolskie</option>
<option value="zachodniopomorskie">zachodniopomorskie</option>
</select>
</td>
</tr>
<tr>
<td align="right"><strong>Telefon<strong style="color:#b32f1c">*</strong></strong></td>
<td align="left"><input maxlength="40" type="Text" style="width:160" name="telefon"> </td>
</tr>
<tr>
<td align="right"><strong>Dochody (zł)</strong></td>
<td align="left">
<select name="dochody" id="dochody" style="width: 150">
<option value="none">wybierz</option>
<option value="-700"><< 700</option>
<option value="700-1400">700 - 1400</option>
<option value="1400-2000">1400 - 2000</option>
<option value="2000-3000">2000 - 3000</option>
<option value="3000-">3000 >></option>
</select>
</td>
</tr>
</table>
</td></tr>
</table>
<br>
Poniższe dane należy wypełnić, jeżeli produkt jest zamawiany przez firmę lub instytucję.
<table cellpadding="4" cellspacing="4" border="0">
<tr>
<td align="right"><strong>Nazwa firmy<br>/instytucji</strong></td>
<td align="left"><input maxlength="40" type="Text" style="width:160" name="nazwa_firmy"> </td>
</tr>
<tr>
<td align="right"><strong>NIP</strong></td>
<td align="left"><input maxlength="40" type="Text" style="width:160" name="nip"> </td>
</tr>
<tr>
<td align="right"><strong>Regon</strong></td>
<td align="left"><input maxlength="40" type="Text" style="width:160" name="regon"> </td>
</tr>
</table>
<input type="Hidden" name="towar" value="<? echo $towar ?>"> </td>
57
<input type="Hidden" name="netto" value="<? echo $netto ?>"> </td>
<input type="Hidden" name="brutto" value="<? echo $brutto ?>"> </td>
<input type="Submit" value="Wy¶lij">
</form>
<br><br>
<?
}
if ( $check == 'confirm' ) {
// Werykikacja; jezeli dane pole jest nie wypelnione zmienna $Zn przyjmuje
// wartosc $Z(n-1) + 1 jezeli na koncu zmienna $Z =/= 0 wraca do formularza
?>
<table cellpadding="2" cellspacing="2" border="0">
<tr>
<td align="right"><strong>Towar</strong></td>
<td align="left"><? echo $towar ?></td>
</tr>
<tr>
<td align="right"><strong>Ilość</strong></td>
<td align="left"><?
if ( $ilosc == '' ) {
echo "<strong style=\"color:#b32f1c\">Nie wypełniłeś tego pola!</strong>";
$z1 = 1;
}
else {
echo $ilosc;
$z1 = 0;
}
?>
</td>
</tr>
<tr>
<td align="right"><strong>Cena brutto</strong></td>
<td align="left"><?
echo $brutto;?> </td>
</tr>
<tr>
<td align="right"><strong>Imię</strong></td>
<td align="left"><?
if ( $imie == '' ) {
echo "<strong style=\"color:#b32f1c\">Nie wypełniłeś tego pola!</strong>";
$z2 = $z1 + 1;
}
else {
echo $imie;
$z2 = 0;
}
?>
</td>
</tr>
<tr>
<td align="right"><strong>Nazwisko</strong></td>
<td align="left"><?
if ( $nazwisko == '' ) {
echo "<strong style=\"color:#b32f1c\">Nie wypełniłeś tego pola!</strong>";
$z3 = $z2 + 1;
}
else {
echo $nazwisko;
$z3 = 0;
}
?>
</td>
</tr>
<tr>
<td align="right"><strong>Ulica/nr domu</strong></td>
<td align="left"><?
if ( $ulica == '' ) {
echo "<strong style=\"color:#b32f1c\">Nie wypełniłeś tego pola!</strong>";
$z4 = $z3 + 1;
}
else {
echo $ulica;
$z4 = 0;
}
?>
</td>
</tr>
<tr>
<td align="right"><strong>Miejscowość</strong></td>
<td align="left"><?
if ( $miejscowosc == '' ) {
echo "<strong style=\"color:#b32f1c\">Nie wypełniłeś tego pola!</strong>";
58
$z5 = $z4 + 1;
}
else {
echo $miejscowosc;
$z5 = 0;
}
?>
</td>
</tr>
<tr>
<td align="right"><strong>Województwo</strong></td>
<td align="left"><?
if ( $wojewodztwo == '' ) {
echo "<strong style=\"color:#b32f1c\">Nie wypełniłeś tego pola!</strong>";
$z6 = $z5 + 1;
}
else {
echo $wojewodztwo;
$z6 = 0;
}
?>
</td>
</tr>
<tr>
<td align="right"><strong>Telefon</strong></td>
<td align="left"><?
if ( $telefon == '' ) {
echo "<strong style=\"color:#b32f1c\">Nie wypełniłeś tego pola!</strong>";
$z7 = $z6 + 1;
}
else {
echo $telefon;
$z7 = 0;
}
?>
</td>
</tr>
<tr>
<td align="right"><strong>Dochody (zł)</strong></td>
<td align="left"><?
if ( $dochody == '' ) {
echo "<strong style=\"color:#b32f1c\">Nie wypełniłeś tego pola!</strong>";
$z8 = $z7 + 1;
}
else {
echo $dochody . " zł";
$z8 = 0;
}
?>
</td>
</tr>
<tr>
<td align="right"><strong>Nazwa firmy<br>/instytucji</strong></td>
<td align="left"><?
if ( $nazwa_firmy == '' ) {
echo "brak";
}
else {
echo $nazwa_firmy;
$z9 = 0;
}
?></td>
</tr>
<tr>
<td align="right"><strong>NIP</strong></td>
<td align="left"><?
if ( $nip == '' ) {
echo "brak";
}
else {
echo $nip;
$z10 = 0;
}
?></td>
</tr>
<tr>
<td align="right"><strong>Regon</strong></td>
<td align="left"><?
if ( $regon == '' ) {
echo "brak";
}
else {
59
echo $regon;
$z10 = 0;
}
?>
</td>
</tr>
</table>
<?
// sprawdzam formularz
$z = $z1 + $z2 + $z3 + $z4 + $z5 + $z6 + $z7 + $z8 + $z9 + $z10;
// jezeli jest ok
if ( $z == '0' ) {
?>
<form action="zakup.php3?wysylaj=tak" method="post">
<input type="Hidden" name="towar" value="<? echo $towar ?>">
<input type="Hidden" name="ilosc" value="<? echo $ilosc ?>">
<input type="Hidden" name="brutto" value="<? echo $brutto ?>">
<input type="Hidden" name="imie" value="<? echo $imie ?>">
<input type="Hidden" name="nazwisko" value="<? echo $nazwisko ?>">
<input type="Hidden" name="ulica" value="<? echo $ulica ?>">
<input type="Hidden" name="miejscowosc" value="<? echo $miejscowosc ?>">
<input type="Hidden" name="wojewodztwo" value="<? echo $wojewodztwo ?>">
<input type="Hidden" name="telefon" value="<? echo $telefon ?>">
<input type="Hidden" name="dochody" value="<? echo $dochody ?>">
<input type="Hidden" name="nazwa_firmy" value="<? echo $nazwa_firmy ?>">
<input type="Hidden" name="nip" value="<? echo $nip ?>">
<input type="Hidden" name="regon" value="<? echo $regon ?>">
<input type="Submit" value="Zamawiam!">
</form>
<?
}
else {
?>
Wymagane pola nie zostały wypełnione. Kliknij "Wstecz" aby to poprawić."
<form action="javascript:history.back();">
<input type="Submit" value="<< Wstecz">
</form>
<?
}
}
else {
exit;
}
}
?>
<br>
</div>
</body>
</html>
60
Rozdział VI – K
IERUNK I DALSZEJ ROZBUDOWY SKLEPU
INTERNETOWEGO
M
ODUŁ INFORMACJI DODATKOWYCH
W trakcie realizacji projektu, w celu uatrakcyjnienia prezentowanych informacji,
skonstruowano mechanizm przechowywania dodatkowych danych o towarach. Ma to na
celu dostarczanie aktualnych danych technicznych oraz funkcjonalnych przeglądającym
ofertę internautom. Prezentowane wyniki wyszukiwania towarów uzupełniono o odnośniki
wywołujące dodatkowe okno z prezentowanymi informacjami. Obecnie trwają prace nad
budową i udostępnieniem formularzy do rejestrowania tych informacji. Z założenia będą one
wykorzystywane przez określone działy firmy do aktualizowania tych informacji oraz
dostępne jedynie z wewnątrz firmy. Informacje te obejmować będą swoim zakresem dane
szczegółowe o towarze, odnośniki do dodatkowych informacji na stronach producentów
sprzętu a także miniaturowe zdjęcia towaru. Po kliknięciu na miniaturowej fotografii
produktu, system załaduje w osobnym oknie normalnej wielkości fotografię, rysunek,
schemat a nawet film z przygotowaną prezentacją. Poniższy listing prezentuje kod
realizujący wygenerowanie okienka i wyświetlenie tych informacji.
<?php
// obsluga info o towarze
// parametry glowne
include "inc/db.inc";
// meta
include "inc/meta.inc";
// phpDB wrapper do baz danych, wkoncu to ma chodzic na wszystkim :)
include "lib/phpDB.inc";
?>
<title>Opis towaru</title>
</HEAD>
<body>
<br>
<?php
// czy mamy jave
if (strpos($HTTP_USER_AGENT, "ozilla") >0) {$jestjava=TRUE;} else {$jestjava=FALSE;};
// sprawdzamy czy akurat trwa ladowanie danych do bazy
// lamerskie, trzeba potem strugnac na lepsze
if ( ! file_exists("/tmp/emarket.tmp"))
{
//$towar z brany jest z tresci zapytania przegladarki trzeba go dobrze wyfiltrowac potem !!!
//trzeba oprogramowac wybor *? jak w dosie
if ($indeks=="" or $indeks==" "){
echo "<SCRIPT LANGUAGE=\"JavaScript\">\n";
echo "<!--\n";
echo "alert('Nie poda¦e czego szukasz!')\n";
echo "history.back()\n";
echo "//-->\n";
echo "</SCRIPT>\n";
echo "<B>Nie poda¦e czego szukasz!</B><BR>";
echo "<A HREF=\"$powrot\">Powrˇt</A>\n";
exit;}
61
//jednorazowe stale i trwale polaczenie z baza
$db=new phpDB();
$db->pconnect($db_host,$db_user,$db_pass,$db_name) or die("Wystapił błąd przy łączeniu! <BR>\n ");
// wersja zapytania po optymalizacji
$sqlquery="SELECT * FROM informacje WHERE indeks=$indeks";
//echo $sqlquery . ";<BR><BR>\n";
$result = $db->execute($sqlquery) or die("Wystąpił błąd przy zapytaniu. <BR>\n");
$numrows=$result->getNumOfRows();
$numfields=$result->getNumOfFields();
/*
Wstepna struktura tabeli z informacjami – może ulec zmianie w trakcie rozwoju modulu
Table = informacje
+----------------------------------+----------------------------------+-------+
| Field | Type | Length|
+----------------------------------+----------------------------------+-------+
| indeks | int4 not null | 4 |
| podtyp | int4 not null default '0' | 4 |
| idpromocja | int4 not null default '0' | 4 |
| pokaz | char() default 'N' | 1 |
| skrot | varchar() default '' | 200 |
| tytul | varchar() default '' | 80 |
| opis | varchar() default '' | 4000 |
| link1 | varchar() default '' | 80 |
| link2 | varchar() default '' | 80 |
| flagi | varchar() default '' | 80 |
+----------------------------------+----------------------------------+-------+
*/
if (strtoupper($result->fields["pokaz"])!="T") {
echo "<div align=center><strong>Brak informacji dodatkowych o tym towarze.</strong></div>";
} else {
echo "<div style=\"margin-left: 20px;\" align=left>";
echo "<div align=left><B>Informacje dodatkowe:</B></div><BR>\n";
echo "<TABLE cellpadding=0 cellspacing=0 border=0 width=550>\n";
echo "<TR>";
echo "<TD align=left valign=top height=1 width=100><B class=\"cena\">Nazwa: </B></TD>";
echo "<TD>" . $nazwa
. " </TD>";
echo "</TR>\n";
echo "<TR>";
echo "<TD align=left valign=top height=1 width=100><B class=\"cena\">Tytul: </B></TD>";
echo "<TD valign=middle>" . $result->fields["tytul"]
." </TD>";
echo "</TR>\n";
echo "<TR>";
echo "<TD align=left valign=top height=1 width=100><B class=\"cena\">Skrˇt: </B></TD>";
echo "<TD valign=middle>" . $result->fields["skrot"]
." </TD>";
echo "</TR>\n";
echo "<TR>";
echo "<TD align=left valign=top height=1 width=100><B class=\"cena\">Opis: </B></TD>";
echo "<TD valign=middle>" . $result->fields["opis"] . " </TD>";
echo "</TR>\n";
echo "<TR>";
echo "<TD align=left valign=top height=1 width=100><B class=\"cena\">Link1: </B></TD>";
echo "<TD valign=middle>" . $result->fields["link1"]
." </TD>";
echo "</TR>\n";
echo "<TR>";
echo "<TD align=left valign=top height=1 width=100><B class=\"cena\">Link2: </B></TD>";
echo "<TD valign=middle>" . $result->fields["link2"]
." </TD>";
echo "</TR>\n";
echo "</TABLE>\n";
echo "</div>\n";
}
//zwalniamy pamiec i zamykamy polaczenie z baza
$result->close();
$db->close();
} //koniec warunku aktualizacji bazy
else { // wlasnie trwa aktualizacja sql'a
echo "<b style=\"color:#DB2614\">";
echo "Przepraszamy! <BR> Właśnie trwa aktualizacja bazy danych.<BR>";
62
echo "Proszę spróbować ponownie za chwilę...<BR><BR></b>";
};
echo "<br>\n";
if ($jestjava) { echo "<a href=\"javascript:window.close()\">zamknij...</a>\n";}
else { echo "<a href=\"http://www.vt.pl/#szukaj\">powrˇt...</a>\n";};
?>
</body></html>
K
OSZYK NA TOWARY
Chcąc w sposób nowoczesny a zarazem wygodny udostępnić klientom możliwość
wirtualnego i swobodnego dodawania do koszyka i wyjmowania zamawianych towarów,
opracowywany jest model wirtualnego koszyka na bazie mechanizmów utrzymywania sesji
w PHP4. Zasadą działania tej metody jest utrzymywanie wirtualnej sesji stałego i trwałego
połączenia, pomiędzy aplikacją działającą na serwerze a przeglądarką klienta. Protokół
HTTP nie został stworzony do tego celu i nie zapewnia trwałego utrzymywania połączenia.
Po wysłaniu danych do przeglądarki połączenie jest zamykane przez serwer. Komplikuje to
w znaczącym stopniu konstruowanie aplikacji wymagających trwałości nawiązanych
połączeń. Wprowadzony mechanizm sesji pozwala na wysyłanie pewnych unikalnych danych
do przeglądarki a po każdej zmianie strony ich zwrotny odbiór. Pozwala to na jednoznaczne
identyfikowanie klienta oraz dywersyfikację zachowania aplikacji w zależności od kontekstu
i użytkownika. Mechanizm sesji realizowany jest bądź przy użyciu „ciasteczek” (pliki
„cookies”), bądź poprzez generowanie tzw. „długich adresów URL”. PHP4 potrafi
samoczynnie rozpoznać czy przeglądarka akceptuje przyjmowanie „cookies” i w przypadku
gdy to nie jest możliwe, przełącza się na posługiwanie długimi adresami. W celu
utrzymywania takiej wirtualnej sesji konieczne jest otwarcie sesji i przekazywanie
identyfikatora sesji pomiędzy kolejnymi wywołaniami stron aplikacji. Dane przypisane do
sesji mogą być przechowywane bądź w bazie danych SQL, bądź jako tymczasowe pliki na
serwerze. Po zakończeniu sesji należy porzucić identyfikator sesji i usunąć przechowywane
dane o sesji. Utrzymywanie sesji można ustanowić na np. 30 minut w celu zachowania
jeszcze przez pewien czas „trwałości” zgromadzonych danych. Gdyby bowiem połączenie
z przeglądarką klienta mogło być utracone na skutek błędów sieci czy też zawieszenia
komputera itp. ponowne połączenie z przeglądarki z niewygasłą sesją „natrafi” na
utrzymywaną sesję po stronie serwera i pozwoli kontynuować realizację zakupów.
Mechanizmy tu przedstawione są szczegółowo opisywane w doskonałej dokumentacji
PHP4 (
www.php.net/manual-lookup.php?pattern=session
).
63
Podsumowanie
Na podstawie doświadczeń zebranych podczas budowy aplikacji sklepu internetowego,
można z całą pewnością stwierdzić, iż na obecnym etapie rozwoju oprogramowania
OpenSource jest możliwe szybkie i efektywne projektowanie oraz wdrażanie różnego
rodzaju systemów i aplikacji sieciowych. Wysoka jakość oprogramowania tworzonego przez
społeczność internetową, gwarantuje zarówno dalszy efektywny rozwój tej idei darmowego
tworzenia systemów, czy też wręcz ustanawiania nowych standardów. Rozwój publicznie
dostępnego oprogramowania umożliwia samodzielne budowanie skomplikowanych
serwisów internetowych, finansowych, naukowych czy rozrywkowych. Kod źródłowy jest
powszechnie dostępny co doskonale wpływa na szybkość poprawiania znalezionych błędów
a tym samym na wysoką jakość tworzonych systemów. Dokumentacja tworzona na potrzeby
rozwijanych narzędzi jest wysokiej jakości „źródłem” niewyczerpanej wprost wiedzy. Projekt
sklepu internetowego początkowo pomyślany jako niewielki program promowania
i sprzedaży towarów, rozwija się szybko w kierunku całego i skomplikowanego systemu,
zapewniającego nie tylko obsługę sprzedaży online. Niejako „przy okazji” powstał cały
serwis internetowy firmy, rozwijane są moduły realizowania zamówień. W planach
przewidziane jest skonstruowanie platformy współpracy i wymiany towarowej pomiędzy
partnerami handlowymi. Korzystając ze zbudowanego zaplecza informatycznego, wdrożono
systemy pocztowe, autoryzacji umów sprzedaży ratalnej, natychmiastowej aktywacji
telefonów PlusGSM i wiele innych. Zastosowane narzędzia pozwoliły na znaczne
oszczędności i były ekonomicznie uzasadnionym rozwiązaniem szczególnie polecanym dla
firm sektora małej i średniej przedsiębiorczości.
64
Spis literatury
[1] Fred Butzen, Dorothy Forbes, „Linux bazy danych” Mikom 1999r
[2] Hans Ladanyi, „SQL Księga eksperta” Helion 2000r
[3] W. Richard Stevens, „UNIX programowanie usług sieciowych” WNT 2000r
[4] Vijay Ahuja, „Bezpieczeństwo w sieciach” Mikom 1997r
[5] Craig Hunt, „TCP/IP Administracja sieci” READ ME 1998r
[6] Æleen Frisch, „UNIX Administracja systemu” READ ME 1997r
[7] D. Brent Chapman, Elizabeth D. Zwicky, „Building Internet Firewalls” O’Reilly 1995r
[8] S. Garfinkel, G. Spafford, „Bezpieczeństwo w UNIXie i Internecie” READ ME 1997r
[9] Bruce Momjian, „PostgreSQL Introduction and Concepts” Addison-Wesley 2001r
[10] „PHP Manual” PHP Documentation Group 2001r
[11] „FreeBSD Handbook” The FreeBSD Documentation Project 2001r
[12] „PostgreSQL Guide” PostgreSQL Global Development Group 2001r
65
Streszczenie - Abstract
Wyższa Szkoła Informatyki i Zarządzania Rzeszów,
2001-07-12
Wydział Administracyjno–Informatyczny
Streszczenie pracy dyplomowej licencjackiej
Implementacja systemu sklepu internetowego
na bazie oprogramowania OpenSource.
Autor: Bartłomiej Siębab
Promotor: Dr inż. Janusz Świerzowicz
Implementacja projektu systemu sklepu internetowego przy wykorzystaniu narzędzi
OpenSource. Zastosowano technologię generowania stron internetowych w oparciu o język
skryptowy PHP, JavaScript, HTML oraz style kaskadowe CSS. Dynamiczne pobieranie
danych z bazy PostgreSQL umożliwiło zbudowanie efektywnego mechanizmu wyszukiwania
i prezentowania towarów.
University of Information Technology and Management
Rzeszów, 2001-07-12
Faculty of Administration and Computer Sciences
Diploma Thesis (BA) Abstract
OpenSource based software implementation
of internet shopping system.
Author: Bartłomiej Siębab
Supervisor: Dr Eng. Janusz Świerzowicz
The purpose of this work was to implement Internet shopping system based on OpenSource
software. Used technology allow dynamically produce HTML pages assembled with PHP,
JavaScript and cascading style sheet (CSS) templates. Efficient database engine based on
PostgreSQL and PHP persistent connections allows developing helpful search and
presentation system.
66
Spis treści
WSTĘP ........................................................................................................................................................................ 2
CEL I ZAKRES PRACY ........................................................................................................................................... 3
ROZDZIAŁ I – ZAŁOŻENIA WSTĘPNE, PRZEDSTAWIENIE METOD I NARZĘDZI................................ 4
U
WARUNKOWANIA EKONOMICZNE
........................................................................................................................... 4
P
REZENTACJA BAZY SYSTEMOWEJ OPROGRAMOWANIA
............................................................................................ 5
ROZDZIAŁ II – TEORIA I FUNKCJONOWANIE............................................................................................... 8
W
YMAGANIA UŻYTKOWNIKÓW
................................................................................................................................ 8
M
ARKETING I REKLAMA
........................................................................................................................................... 9
Z
ASADA DZIAŁANIA
................................................................................................................................................ 11
M
ODEL LOGICZNY I FUNKCJONALNY
...................................................................................................................... 12
ROZDZIAŁ III – PRZYGOTOWANIE DANYCH............................................................................................... 15
KONWERSJA DANYCH
.............................................................................................................................................. 15
ARCHIWIZACJA I PRZESYŁANIE DANYCH
................................................................................................................. 17
S
TRUKTURA KONWERTOWANYCH PLIKÓW
............................................................................................................. 18
K
ONWERSJA STRONY KODOWEJ
.............................................................................................................................. 19
Z
AŁADOWANIE DANYCH DO BAZY
P
OSTGRE
SQL ................................................................................................... 20
ROZDZIAŁ IV – STRUKTURA I ORGANIZACJA BAZY EMARKET .......................................................... 24
S
KRYPTY ADMINISTRACYJNE
.................................................................................................................................. 24
S
KRYPTY
SQL ........................................................................................................................................................ 29
Kategoria skryptów „create” ........................................................................................................................... 29
Kategoria skryptów „delete”............................................................................................................................ 32
Kategoria skryptów „load” .............................................................................................................................. 33
Kategoria skryptów „check” ............................................................................................................................ 35
Kategoria skryptów „analysis” ........................................................................................................................ 35
S
TRUKTURA BAZY
.................................................................................................................................................. 36
ROZDZIAŁ V – WYSZUKIWARKA TOWARÓW ............................................................................................. 38
K
ONCEPCJA DZIAŁANIA
.......................................................................................................................................... 38
K
OD ŹRÓDŁOWY WYSZUKIWARKI
........................................................................................................................... 42
ROZDZIAŁ VI – KIERUNKI DALSZEJ ROZBUDOWY SKLEPU INTERNETOWEGO ............................ 60
M
ODUŁ INFORMACJI DODATKOWYCH
..................................................................................................................... 60
K
OSZYK NA TOWARY
.............................................................................................................................................. 62
PODSUMOWANIE .................................................................................................................................................. 63
SPIS LITERATURY................................................................................................................................................. 64
STRESZCZENIE - ABSTRACT ............................................................................................................................. 65
SPIS TREŚCI ............................................................................................................................................................ 66