Uzyszkodnicy i aplikacja, cz. II

Użytkowników w bazie powiedzmy, że już mam. Teraz wypadałoby zrobić coś, aby dać im możliwość zarejestrowania się w serwisie.

Proces ten jest prosty jak konstrukcja gwoździa. Wpisujemy login, wpisujemy hasło (dwa razy), wpisujemy napisik z CAPTCHA i po walidacji formularza ( np. nick unikalny i hasło nie za krótkie ) tworzone jest konto w serwisie. Do tego można się na nie nawet zalogować! Zasadniczo skoro domenę już mamy (przynajmniej na początek), widok sobie zaraz stworzymy, kontroler też ogarniemy. Jedyną rzeczą, o której póki co nie za bardzo mam pojęcie to wdrożenie CAPTCHY. Sensowność jej wdrożenia polega na tym, iż jest to plugin do frameworka, a takowymi jeszcze się nie bawiłem (no chyba że skryptami/pluginami do wrzucenia aplikacji na serwer mor.ph).

Dla procesu rejestracji oraz późniejszego logowania stworzyłem w poprzednim wpisie kontroler Users. Teraz należy usunąć z niego wpis do scaffoldingu i zabrać się do samodzielnej pracy. Tworzymy akcję register, a także stosowny widok. Kod kontrolera jest oczywisty,  widok (registry.gsp) to też prosty formularz. Wkleiłbym kod (używam tam znaczników GSP), ale niestety WordPress wygrał ze mną i za cholery nie dało się mimo wpisania encji, etc. umieścić tego kodu tutaj w ładnej formie. Chyba czas przesiąść się na Joggera czy coś.

Pluginy do Grailsów możemy znaleźć na stronie domowej projektu. Ja postanowiłem użyć JCAPTCHA, polecanego w książce “Beginning Groovy and Grails”. Co prawda w treści wyżej wymienionej pozycji używa się prostszego rozwiązania, nie ma sensu przepisywać kodu, lepiej czegoś nowego się nauczyć. JCAPTCHA jest oficjalnym rozszerzeniem frameworka zatem wystarczy w konsoli wpisać (w katalogu naszej aplikacji):

grails install-plugin jcaptcha

By po chwili mielenia cieszyć się zaimportowanym rozszerzeniem. Tutaj dopiero zaczyna się przygoda. JCAPTCHA to nie w kij dmuchał i z nim trzeba już się trochę nagimnastykować. Wpierw trzeba wyedytować plik konfiguracyjny:

jcaptchas

{

image = new GenericManageableCaptchaService(

new GenericCaptchaEngine(

new GimpyFactory(

new RandomWordGenerator("abcdefghijklmnopqrstuvwxyz1234567890"),

new ComposedWordToImage(new RandomFontGenerator(

20, 30, [new Font("Arial", 0, 10)] as Font[]),

new GradientBackgroundGenerator(140, 35,

new SingleColorGenerator(Color.white),

new SingleColorGenerator(new Color(152, 245, 255))),

new NonLinearTextPaster(6, 6, new Color(108, 123, 139))

)

)

),

180,

180000)

}

Nie pytajcie co i z czym. Gotowca ściągnąłem z Disco Bloga. Dla mnie wystarczy, gdybym zaczął kombinować, aby tło było różowe, a literki czarne i do tego robiło BLING BLING co 2 sekundy to może bym się wgłębiał w meandry tego zapisu. Nie muszę to i nie zawracam sobie głowy. Na samym początku pliku należy dodać importy odpowiednich plików – lista pod ww. adresem. Dodać też trzeba linijkę w kodzie widoku, aby wrzucił nasz anty-spam do formularza:

<input type="text" id="captcha"  name="captcha" value=""/>

<jcaptcha:jpeg name="image"/>

Oto zdjęcie z efektem mojej pracy:

register_captcha1

Pozostaje tylko napisać kod kontrolera i logikę. Logika jest prosta – walidacja przekazanych danych i lecimy do przodu. Najistotniejszy wydaje się być kontroler, a także mechanizm do pokazywania błędów walidacyjnych (wrzucę to do layoutu by działał uniwersalnie wszędzie). Wymienione powyżej funkcjonalności głupio byłoby powielać w co drugim kontrolerze – choć i to jest metoda, przynajmniej w przypadku walidacji. Jednakże tutaj będę mądry i stworzymy takie coś jak service, czyli usługę. Jest to po prostu oddzielna klasa, która oferuje pewną funkcjonalność, a którą możemy łatwo wywołać z poziomu kontrolera. Co więcej – usługisingletonami (domyślnie) z całym dobrodziejstwem inwentarza.

Usługę utworzyć prosto – prawyklik na katalogu Services w NetBeans, wpisujemy nazwę klasy (u mnie formValidation) i mamy gotową usługę. Mocą konwencji do nazwy klasy jest dopisywany suffix ‘Service’. Oto wygenerowany kod:

class FormValidateService {boolean transactional = truedef serviceMethod() {}

}

W miejsce serviceMethod rzecz jasna trzeba wcisnąć nazwę metody, a także jej ciało. Kiedy tak się stanie mamy gotową na każde wezwanie usługę. Ja postanowiłem stworzyć dwie takowe – jedną uniwersalną do walidacji pól formularza, a także jeszcze jedną do translacji nazw pól (pole ‘name’ w elementach formularza) na polskie odpowiedniki celem ich ładnego wyświetlenia przy ew. błędzie walidacji. Oto kod usługi formValidate:

class FormValidateService {static int FIELD_INCORRECT = 0;

static int FIELD_COMPARE_INCORRECT = 1;

String fieldNamePl;

String fieldConfirmNamePl;

String defaultMessage;

boolean transactional = true

def translateFieldServiceHashMap validateForm( ArrayList Data ) {

HashMap dataValidated = new HashMap();

Boolean isValidated = true;

for( HashMap mapParams : Data ) {

if( mapParams.getAt('paramGiven') == mapParams.getAt('paramExpected') ) {

// dataValidated.putAt( mapParams.get('index'), true );

} else {

fieldNamePl = translateFieldService.translateName( mapParams.get('index') );

fieldConfirmNamePl = translateFieldService.translateName( mapParams.get('index_confirm') );

int compareStatus = mapParams.getAt('compareStatus');

if( compareStatus == FIELD_INCORRECT ) {

defaultMessage = 'Pole [' + fieldNamePl + '] ma niepoprawną wartość!';

}

else if( compareStatus == FIELD_COMPARE_INCORRECT ) {

defaultMessage = 'Pole [' + fieldNamePl + '] ma wartość inną niż pole [' + fieldConfirmNamePl + ']';

}

else {

defaultMessage = 'Pole [' + fieldNamePl + '] ma niepoprawną wartość!';

}

dataValidated.putAt( mapParams.get('index'), new org.springframework.validation.FieldError( 'FormValidateService', fieldNamePl, fieldNamePl, true, new String[1], new Object[1], defaultMessage ) );

isValidated = false;

}

}

return [ validated: isValidated , data: dataValidated ];

}

}

Jak już wspomniałem usługi to singletony (jest to zachowanie domyślne, ale jak wszystko w Grails daje się to zmienić), a odwoływanie się do nich jest banalne. Wystarczy w kontrolerze Users wpisać taki kod:

def formValidateService

I od tej pory do każdej z metod tejże usługi możemy odwoływać się z dowolnego miejsca w kontrolerze. Jak widać na powyższym przykładzie z poziomu kodu usługi możemy odwoływać się do funkcjonalności innej usługi. Oto jeszcze kod małej usługi tłumaczącej – można to oczywiście oprzeć o domyślne ustawienia i plikach z domyślnymi komunikatami. Póki co działa to w takiej formie, gdyż wówczas mamy dowolność – trzymania komunikatów np. w bazie danych, niekoniecznie zaś na plikach (co ułatwia często pracę). Kod:

class TranslateFieldService {boolean transactional = trueString translateName( String toTranslate ) {

String returnString = '';

HashMap translations = new HashMap();

translations.put( 'login', 'login' );

translations.put( 'passwd', 'hasło' );

translations.put( 'passwd_confirm', 'potwierdzenie hasła' );

returnString = ( translations.containsKey( toTranslate ) ) ? translations.getAt( toTranslate ) : toTranslate ;

return returnString;

}

}

I tyle nasze usługi. Powiedzmy, że dają one pewną funkcjonalność, choć z całą pewnością można by je ulepszyć. Dla naszej aplikacji ważne będzie też miejsce, gdzie możnaby wyświetlić informacje przeznaczone dla uzytkownika (błędy walidacji, informacje o charakterze neutralnym). By to osiągnąć dodałem trochę magii w pliku layoutu main.gsp, ale nie jest to nic wykraczającego poza zwykły IF. No i teraz nadszedł czas na kontroler:

class UsersController {def defaultAction = "index"

def formValidateService

def JcaptchaService

def index = { }

def register = {

if( session.user == null ) {

HashMap validationResults;

if( request.method == "POST" ) {

validationResults = formValidateService.validateForm(

[ [index: 'passwd', index_confirm: 'passwd_confirm',paramGiven: params.passwd.toLowerCase(),paramExpected: params.passwd_confirm.toLowerCase(), compareStatus: formValidateService.FIELD_COMPARE_INCORRECT ],

[index: 'captcha', paramGiven: JcaptchaService.validateResponse( 'image', session.id, params.captcha), paramExpected: true ] ] )

if( validationResults.getAt('validated') == true ) {

Htj_Users user = new Htj_Users( nick: params.nick, passwd: params.passwd.encodeAsMD5(), role: Htj_User_Role.get(1) );

if( user.save() ) {

session.user = user

flash.message = 'Rejestracja zakończona pomyślnie'

redirect(uri: '/')

} else {

flash.errors = user.errors.getAllErrors()

}

} else {

flash.errors = ((HashMap) validationResults.getAt('data')).values()

}

[ params: params ]

}

} else {

flash.message = 'Użytkownik już jest zalogowany!'

redirect( uri: '/' )

}

}

def login = {

if( session.user == null ) {

if( request.method == 'POST' ) {

Htj_Users user =

Htj_Users.findByNick(

params.login )

if( user != null ) {

session.user = user;

redirect( uri: '/' );

} else {

HashSet errors = new HashSet();

errors.add(new org.springframework.validation.FieldError( 'FormValidateService', 'login', 'login', true, new String[1], new Object[1], 'Podano złe dane!' ));

flash.errors = errors

}

}

} else {

flash.message = 'Już jesteś zalogowany!'

redirect( uri: '/' )

}

}

def logout = {

session.user = null;

flash.message = 'Zostałeś pomyślnie wylogowany!'

redirect( uri: '/' );

}

}

Jak przekonuję się po raz kolejny WordPress ma strasznie kijowe formatowanie kodu. Dlatego też sugeruję skopiowanie powyższego do IDE i przejrzenie kodu w bardziej przyjaznej formie. Myślę, że nie powinien on sprawić trudności. Poniżej kilka screenów z efektów działania programu:

Błąd rejestracji:

bledyrejestracji

Pomyślna rejestracja i zalogowanie:

zarejestrowanopomyslnie

To tyle. Oczywiście w realnie działającej aplikacji tak przeprowadzony proces rejestracji wołałby o pomstę do nieba – najlepiej byłoby dodać dla użytkowników pole e-mail, aby można było przesłać na wskazany adres mejl aktywujący (no i później w aplikacji wygodniej byłoby posiadać adresy swoich użytkowników). Również usability pewnikiem kuleje, podobnie jak sam kod 🙂 Jednakże tak jakoś głupio wyszło, że napisanie tego posta zajęło mi dwa tygodnie więc podaruję sobie te małe błędy.

Advertisements

4 thoughts on “Uzyszkodnicy i aplikacja, cz. II

  1. chlebik Post author

    To problem z WordPressem, kombinuje jakby to ulepszyc, ale poki co na darmowych skorkach kiepsko to wyglada. Dlatego powoli zaczynam myslec o jakiejs migracji, albo o podpieciu kodu pod jakis parser HTMLa, bo faktycznie przy kilku linijkach wiecej wyglada to topornie.

  2. chlebik Post author

    Serwery WP nie pozwalają na umieszczanie własnego JSa i CSSa (no chyba, że każdy element kodu bym ostylował, ale to powrót do HTMLa 3). Gdybym uruchamiał to na WP, ale na własnym serwerze to mógłbym wyczyniać cuda. Niestety na razie jest jak jest – być może pójdę po najmniejszej linii oporu i ładnie sformatowane kody będę umieszczał na zewnętrznym serwerze i po prostu dorzucał link do niego.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s