Пишемо з IoC Starter. Базовий маппінг запитів, використовуючи context, web і orm

 

Введення

 

З моменту першого релізу пройшло досить багато часу (посилання на попередню статтю). Що змінилося?

  • покращено стабільність системи в цілому;
  • реалізована лінива завантаження компонентів;
  • вбудована базова система слухачів;
  • вбудована підтримка аспектно-орієнтованого програмування (для середньої складності вирішення задач, в іншому все ж раджу використовувати — AspectJ бібліотеку)
  • новий завантажувач RequestFactory

 

**Модулі

 

  • модуль роботи з кешем на базі EhCache, Guava
  • модуль роботи з потоками (як ініціалізація допомогою анотації @SimpleTask, так і пряма робота з пулом)
  • модуль роботи з базою (легкий ORM з підтримкою JPA, Transactions, NO-SQL Driver — Orient, Crud methods, repository system і автогенерацией запитів з функції класу-репозиторію)
  • модуль роботи з веб-мордою (маппінг лінків допомогою анотацій, підтримка кастомних producers/consumes, Velocity Template Rendering Page, Basic Security Requests, Sessions, Cookies, SSL) на базі Netty 4.1.30.Final

 

Структура фреймворку

 

“Це звичайно все добре,”- скажете Ви, -“але по факту чи працює це все?”.
“Так, працює. Прошу під кат”.

 

Процес реалізації прикладу

 

Для реалізації прикладу я буду використовувати Maven 3 і Intelijj Idea 2018.2.

 

1) Підключаємо залежності:

 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>example-webapp</artifactId>
<groupId>org.ioc</groupId>
<packaging>jar</packaging>

<version>0.0.1</version>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
``1.8</source>
<target>1.8</target>
</configuration>

<executions>
<execution>
<id>default-testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>org.ioc</groupId>
<artifactId>context-factory</artifactId>
<version>2.2.4.STABLE</version>
</dependency>

<dependency>
<groupId>org.ioc</groupId>
<artifactId>orm-factory</artifactId>
<version>2.2.4.STABLE</version>
</dependency>

<dependency>
<groupId>org.ioc</groupId>
<artifactId>web-factory</artifactId>
<version>2.2.4.STABLE</version>
</dependency>
</dependencies>

<repositories>
<repository>
<id>context</id>
<url>https://raw.github.com/GenCloud/ioc_container/context</url>
</repository>
<repository>
<id>cache</id>
<url>https://raw.github.com/GenCloud/ioc_container/cache</url>
</repository>
<repository>
<id>threading</id>
<url>https://raw.github.com/GenCloud/ioc_container/threading</url>
</repository>
<repository>
<id>orm</id>
<url>https://raw.github.com/GenCloud/ioc_container/orm</url>
</repository>
<repository>
<id>web</id>
<url>https://raw.github.com/GenCloud/ioc_container/web</url>
</repository>
</repositories>
</project>

 

**На maven central поки не переїхав, на жаль.

 

Структура проекту:

Стандартний MVC патерн, чи не правда?

 

Створимо точку входу в програму:

package org.examples.webapp;

import org.ioc.annotations.context.ScanPackage;
import org.ioc.annotations.modules.CacheModule;
import org.ioc.annotations.modules.DatabaseModule;
import org.ioc.annotations.modules.ThreadingModule;
import org.ioc.annotations.modules.WebModule;
import org.ioc.context.starter.IoCStarter;

@WebModule
@CacheModule
@ThreadingModule
@DatabaseModule
@ScanPackage(packages = {"org.examples.webapp"})
public class AppMain {
 public static void main(String[] args) {
IoCStarter.start(AppMain.class);
}
}

 

**Пояснення:
Анотація @ScanPackages — визначає контексту пакети для виявлення компонентів (в народі — “бинов”).
Анотація @WebModule — служить для підключення і ініціалізації web фабрики.
Анотація @CacheModule — служить для підключення і ініціалізації фабрики кеша, використовується для коректної роботи ORM (в майбутніх версіях анотація не буде вимагатися).
Анотація @ThreadingModule — служить для підключення і ініціалізації фабрики потоків, використовується для коректної роботи web фабрики (в майбутніх версіях анотація не буде вимагатися).
Анотація @DatabaseModule — служить для підключення і ініціалізації фабрики ORM.
Всі фабрики мають дефолтні конфігуратори, які можна змінити на свої з перевизначенням функцій використовуються налаштувань фабриками (у кожної анотації модуля перевизначений клас конфігуратор — Class<?> autoConfigurationClass() default WebAutoConfiguration.class), або ж відключити будь-яку конфігурацію за допомогою анотації @Exclude в main класі.
Утиліта IoCStarter — головний клас-инициализатор контексту.

Ну начебто все готово, контекст ініціалізується, веб працює на дефолтному порту 8081, але лінкування немає і при переході на сайт нам нічого толком не видає. Що ж, йдемо далі.

Створимо конфігураційний файл для наших модулів. По дефолту всі конфіги вантажаться з {working_dir}/configs/default_settings.properties — його і створимо за відповідним шляху.

# Threading
ioc.threads.poolName=shared
ioc.threads.availableProcessors=4
ioc.threads.threadTimeout=0
ioc.threads.threadAllowCoreTimeOut=true
ioc.threads.threadPoolPriority=NORMAL
# Event dispather
# кількість дескрипторів (процесорів) для обробки слухачів (асинхронне виконання)
ioc.dispatcher.availableDescriptors=4
# Cache
# фабрика кеша (EhFactory|GuavaFactory)
cache.factory=org.ioc.cache.impl.EhFactory
# Datasource
# тип бази (локальна-власна локальна-серверна або віддалена)
#LOCAL, LOCAL_SERVER, REMOTE
datasource.orient.database-type=LOCAL
# місцезнаходження бази
datasource.orient.url=./database
# ім'я бази даних (для локальної не обов'язково)
datasource.orient.database=orient
# користувач бази
datasource.orient.username=admin
# пароль користувача
datasource.orient.password=admin
# конфігурація для мапінгу сутностей в базу (create, dropCreate, refresh, none)
datasource.orient.ddl-auto=dropCreate
# конфігурація повідомляє менеджеру, показувати згенеровані запити чи ні
datasource.orient.showSql=true
# Web server
# порт роботи веб-сервера
web.server.port=8081
# потрібен SSL обробник
web.server.ssl-enabled=false
# in seconds
# таймаут сесій (дефолтний 7200 сек. = 2 години)
web.server.security.session.timeout=300
# кодування веб-морди
web.server.velocity.input.encoding=UTF-8
web.server.velocity.output.encoding=UTF-8
# завантажувач веб-морди
web.server.velocity.resource.loader=file
# клас завантажувача
web.server.velocity.resource.loader.class=org.apache.velocity.runtime.resource.loader.FileResourceLoader
# шлях до знаходиться веб-морді
web.server.velocity.resource.loading.path=./public

 

Далі, нам потрібні сутність користувача і її керуючий репозиторій:
Реалізація сутності TblAccount:

 

package org.examples.webapp.domain.entity;

import org.ioc.web.security.user.UserDetails;

import javax.persistence.*;
import java.util.Collections;
import java.util.List;

@Entity
public class TblAccount implements UserDetails {
@Id
 @GeneratedValue(strategy = GenerationType.SEQUENCE)
 private long id;

 @Column(name = "username")
 private String username;

 @Column(name = "password")
 private String password;

@Transient
 private String repeatedPassword;

 public String getRepeatedPassword() {
 return repeatedPassword;
}

 public void setRepeatedPassword(String repeatedPassword) {
 this.repeatedPassword = repeatedPassword;
}

@Override
 public String getUsername() {
 return username;
}

 public void setUsername(String username) {
 this.username = username;
}

@Override
 public String getPassword() {
 return password;
}

 public void setPassword(String password) {
 this.password = password;
}

@Override
 public List<String> getRoles() {
 return Collections.singletonList("ROLE_USER");
}
}

 

Читайте також  Цікаві задачі з технічних співбесід

**Пояснення:
Все стандартно, як і у всіх JPA-підтримують фреймворків. Маппим сутність з допомогою @.Entity, створюємо Primary Key за допомогою анотації @.Id, маппим колонки з допомогою анотації @Column. і унаследуемся від UserDetails для ідентифікації сутності Security модулі.

Реалізація репозиторію сутності TblAccountRepository:

package org.examples.webapp.domain.repository;

import org.examples.webapp.domain.entity.TblAccount;
import org.ioc.annotations.context.IoCRepository;
import org.ioc.orm.repositories.CrudRepository;

import javax.transaction.Transactional;

@IoCRepository
public interface TblAccountRepository extends CrudRepository<TblAccount, Long> {
@Transactional
 TblAccount findByUsernameEq(String username);
}

 

**Пояснення:
Анотація @IoCRepository — служить для визначення контекстом, що клас є репозиториеми його потрібно обробити “по-іншому”.
Підтримує стандартні CRUD функції:

  • Entity fetch(ID id) — дістає сутність типу Entity з бази, або з кеша за Primary Key;
  • List fetchAll() — дістає всі сутності типу Entity, попередньо завантажуючи їх в кеш;
  • void save(Entity entity) — створює/оновлює сутність типу Entity як у базі, так і в кеші;
  • void delete(Entity entity) — видаляє сутність типу Entity як з бази так і з кешу;
  • boolean exists(ID id) — перевіряє наявність сутності в базі за Primary Key.
    Всі CRUD-запити відбуваються в транзакції.

Підтримує автогенерацію запитів допомогою перевизначення функцій з ключовими словами, як в реалізації вище (TblAccount findByUsernameEq(String username)) і виклик зареєстрованих запитів (NamedQuery)

Функція findByUsernameEq(String username) — здійснює пошук сутності за її полю username. Згенерований запит:

 select * from tbl_account where username = 'username'

Далі нам знадобитися, рівень для управління бізнес-логікою.
Реалізації AccountService:

package org.examples.webapp.service;

import org.examples.webapp.domain.entity.TblAccount;
import org.examples.webapp.domain.repository.TblAccountRepository;
import org.examples.webapp.responces.IMessage;
import org.ioc.annotations.context.IoCComponent;
import org.ioc.annotations.context.IoCDependency;
import org.ioc.web.model.http.Request;
import org.ioc.web.security.configuration.SecurityConfigureAdapter;
import org.ioc.web.security.encoder.bcrypt.BCryptEncoder;
import org.ioc.web.security.user.UserDetails;
import org.ioc.web.security.user.UserDetailsProcessor;

import java.util.Objects;

import static org.examples.webapp.responces.IMessage.Type.ERROR;
import static org.examples.webapp.responces.IMessage.Type.OK;

@IoCComponent
public class AccountService implements UserDetailsProcessor {
@IoCDependency
 private TblAccountRepository tblAccountRepository;

@IoCDependency
 private BCryptEncoder bCryptEncoder;

@IoCDependency
 private SecurityConfigureAdapter securityConfigureAdapter;

@Override
 public UserDetails loadUserByUsername(String username) {
 return tblAccountRepository.findByUsernameEq(username);
}

 public void save(TblAccount tblAccount) {
tblAccountRepository.save(tblAccount);
}

 public void delete(TblAccount tblAccount) {
tblAccountRepository.delete(tblAccount);
}

 public IMessage tryCreateUser(String username, String password, String repeatedPassword) {
 if (username == null || username.isEmpty() || password == null || password.isEmpty()
 || repeatedPassword == null || repeatedPassword.isEmpty()) {
 return new IMessage(ERROR, "Invalid request parameters!");
}

 if (!Objects.equals(password, repeatedPassword)) {
 return new IMessage(ERROR, "Repeated password doesn't match!");
}

 final UserDetails userDetails = loadUserByUsername(username);
 if (userDetails != null) {
 return new IMessage(ERROR, "Account already exists!");
}

 final TblAccount account = new TblAccount();
account.setUsername(username);
account.setPassword(bCryptEncoder.encode(password));

save(account);
 return new IMessage(OK, "Successfully created!");
}

 public IMessage tryAuthenticateUser(Request request, String username, String password) {
 if (username == null || username.isEmpty() || password == null || password.isEmpty()) {
 return new IMessage(ERROR, "Invalid request parameters!");
}

 final UserDetails userDetails = loadUserByUsername(username);
 if (userDetails == null) {
 return new IMessage(ERROR, "Account not found!");
}

 if (!bCryptEncoder.match(password, userDetails.getPassword())) {
 return new IMessage(ERROR, "Password does not match!");
}

 securityConfigureAdapter.getContext().authenticate(request, userDetails);
 return new IMessage(OK, "Successfully authenticated");
}

 public IMessage logout(Request request) {
 if (securityConfigureAdapter.getContext().removeAuthInformation(request)) {
 return new IMessage(OK, "/");
}

 return new IMessage(ERROR, "Credentials not found or not authenticated!");
}
}

**Пояснення:
Анотація @IoCComponent — служить для ініціалізації класу як компонента.
Анотація @IoCDependency — служить для впровадження залежностей в інстанси класу.
Утиліта BCryptEncoder — реалізація кодека BCrypt для шифрування пароля (поки що єдині кодек).
Системний інстанси SecurityConfigureAdapter — служить для роботи з маппінгом запитів і сессиий користувачів.
Функція UserDetails loadUserByUsername — успадковується функція UserDetailsProcessor, служить для завантаження користувача сесію і виставлення прапора аутентифікації (в майбутньому для стандартного мапінгу авторизації Security)
Функція IMessage tryCreateUser — функція створення користувача.
Функція IMessage tryAuthenticateUser — функція аутентифікації користувача.
Функція IMessage logout — функція очищення сесії від авторизованого користувача.
Клас IMessage — клас-утиліта для виведення потрібної нам інформації в браузері (json-відповідь).

package org.examples.webapp.responces;

public class IMessage {
 private final String message;
 private final Type type;

 public IMessage(String message) {
 this.message = message;
 type = Type.OK;
}

 public IMessage(Type type String message) {
 this.message = message;
 this.type = type;
}

 public String getMessage() {
 return message;
}

 public Type getType() {
 return type;
}

 public enum Type {
OK,
ERROR
}
}

Тепер знадобиться реалізація самого лінкування (мапінгу запитів):

package org.examples.webapp.mapping;

import org.examples.webapp.domain.entity.TblAccount;
import org.examples.webapp.responces.IMessage;
import org.examples.webapp.service.AccountService;
import org.ioc.annotations.context.IoCDependency;
import org.ioc.annotations.web.IoCController;
import org.ioc.web.annotations.Credentials;
import org.ioc.web.annotations.MappingMethod;
import org.ioc.web.annotations.RequestParam;
import org.ioc.web.annotations.UrlMapping;
import org.ioc.web.model.ModelAndView;
import org.ioc.web.model.http.Request;

@IoCController
@UrlMapping("/")
public class MainMapping {
@IoCDependency
 private AccountService accountService;

@UrlMapping
 public ModelAndView index() {
 final ModelAndView modelAndView = new ModelAndView();
modelAndView.setView("index");
 return modelAndView;
}

 @UrlMapping(value = "signup", method = MappingMethod.POST)
 public IMessage createUser(@RequestParam("username") String username,
 @RequestParam("password") String password,
 @RequestParam("repeatedPassword") String repeatedPassword) {
 return accountService.tryCreateUser(username, password, repeatedPassword);
}

 @UrlMapping(value = "signin", method = MappingMethod.POST)
 public IMessage auth(Request request,
 @RequestParam("username") String username,
 @RequestParam("password") String password) {
 return accountService.tryAuthenticateUser(request, username, password);
}

@UrlMapping("signout")
 public IMessage signout(Request request) {
 return accountService.logout(request);
}

@UrlMapping("loginPage")
 public ModelAndView authenticated(@Credentials TblAccount account) {
 final ModelAndView modelAndView = new ModelAndView();
modelAndView.setView("auth");

 modelAndView.addAttribute("account", account);
 return modelAndView;
}
}

**Пояснення:
Анотація @IoCController — служить для ідентифікації класу в контексті, як контролера (маппера запитів браузера)
Анотація @UrlMapping — вказує, що потрібно проаналізувати функцію/клас на наявність запитів, оброблюваних хендлер каналу.
Параметри:

  • value — потрібний нам запит;
  • method — http метод для обробки (GET, POST, PUT, etc.);
  • consumes — http mime type для перевірки наявності запиту конкретного типу (опціонально);
  • produces — http content-type для віддачі у відповіді конкретного типу вмісту (Content-Type: text/html; charset=utf-8 Content-Type: multipart/form-data; boundary=something, etc. опціонально;
Читайте також  Відмінності Phoenix і Rails очима новонаверненого

Анотація @RequestParam — служить для визначення імені одержуваного параметра запиту. Оскільки остільки дефолтними засобами рефлексії можна отримати поточне ім’я параметра методу, мені було лінь підключати зайву залежність javaassist, шаманити з асмом. Тому такий собі метод визначення імені параметра для впровадження цього параметру значення, отриманого запиту. Існує аналог для GET типу — @PathVariable — той же самий принцип роботи (не сумісний з POST).
Анотація @Credentials — служить для вставки поточних даних авторизованого користувача, в іншому випадку може бути null, якщо інформації авторизованого користувача немає в сесії.
Системний клас Request — поточна інформація про надійшов запит, що містить в собі коки, хидеры і канал користувача, який надалі можна буде відправляти Push message’s… в кого яка фантазія вже на цей рахунок.
Клас-утиліта ModelAndView — модель сторінки з ім’ям ресурсу без розширення, і атрибутами для впровадження в ресурс.

Начебто все, але немає — потрібно обов’язково сконфігурувати доступний маппінг запитів для користувачів.

package org.examples.webapp.config;

import org.ioc.annotations.configuration.Property;
import org.ioc.annotations.configuration.PropertyFunction;
import org.ioc.web.security.configuration.HttpContainer;
import org.ioc.web.security.configuration.SecurityConfigureProcessor;
import org.ioc.web.security.encoder.Encoder;
import org.ioc.web.security.encoder.bcrypt.BCryptEncoder;
import org.ioc.web.security.filter.CorsFilter;
import org.ioc.web.security.filter.CsrfFilter;

@Property
public class SecurityConfig implements SecurityConfigureProcessor {
@Override
 public void configure(HttpContainer httpContainer) {
httpContainer.
configureRequests().
 anonymousRequests("/", "/signup", "/signin").
resourceRequests("/static/**").
 authorizeRequests("/loginPage", "ROLE_USER").
 authorizeRequests("/signout", "ROLE_USER").
and().
configureSession().
expiredPath("/");
}

@PropertyFunction
 public CsrfFilter csrfFilter() {
 return new CsrfFilter();
}

@PropertyFunction
 public CorsFilter corsFilter() {
 return new CorsFilter();
}

@PropertyFunction
 public Encoder encoder() {
 return new BCryptEncoder();
}
}

 

**Пояснення:
Анотація @Property — повідомляє контексту, що це конфігураційний файл і його потрібно ініціалізувати.
Анотація @PropertyFunction — повідомляє аналізатору конфігурацій, що ця функція повертає якийсь тип і повинен його ініціалізувати як компонент (бін).
Інтерфейс SecurityConfigureProcessor — утиліта служить для конфігурації мапінгу запитів.
Клас-модель HttpContainer — утиліта, що зберігає в собі маппінг запитів, вказаних користувачем.
Клас CsrfFilter — фільтр не валідних запитів (реалізації механіки CSRF).
Клас CorsFilter — фільтр Cross-Origin Resource Sharing.

 

Функція anonymousRequests — приймає в себе необмежений масив запитів, не вимагає авторизованих користувачів і перевірки ролей (ROLE_ANONYMOUS).
Функція resourceRequests — приймає в себе необмежений масив запитів, конкретно служить для визначення, яким шляхом буде лежати ресурсний файл, не вимагає складної обробки (css, js, images, etc.).
Функція authorizeRequests — приймає в себе необмежений масив запитів, вимагає авторизованого користувача та конкретну роль, властиву користувачеві.
Функція expiredPath — при очищенні минулого часу сесії, користувача перекидає з цього маппингу (redirect link).

Що ж, залишилися сторінки, скрипти і стилі сайту (глибоко заглиблюватися не буду).

Заголовок спойлера

index.vm — головна сторінка

 

<html>
<head>
 <meta charset="utf-8"/>
 <title>IoC Test</title>
 <link rel="stylesheet" href="/static/css/bootstrap.min.css">

 <link rel="stylesheet" href="/static/css/style.css"/>
 <link rel="stylesheet" href="/static/css/pnotify.custom.min.css"/>
 <link rel="stylesheet" href="/static/css/pnotify.css"/>
 <link rel="stylesheet" href="/static/css/pnotify.buttons.css"/>
</head>
<body>
<div class="container">
 <h1>IoC Starter Test</h1>
<br>
 <h4>Create user</h4>
<br>
 <form id="creation">
 <label for="username">Username: </label>
 <input type="text" id="username" name="username" class="color-input-field"/>
 <label for="password">Password: </label>
 <input type="password" id="password" name="password" class="color-input-field"/>
 <label for="repeatedPassword">Repeate: </label>
 <input type="password" id="repeatedPassword" name="repeatedPassword" class="color-input-field"/>
 <button type="button" class="btn btn-success btn-create">Sing up!</button>
</form>

<h4>Authenticate</h4>
<br>
 <form id="auth">
 <label for="username">Username: </label>
 <input type="text" id="username" name="username" class="color-input-field"/>
 <label for="password">Password: </label>
 <input type="password" id="password" name="password" class="color-input-field"/>
 <button type="button" class="btn btn-danger btn-auth">Sing in!</button>
</form>
</div>

<script type="text/javascript" src="/static/js/jquery.js"></script>
<script type="text/javascript" src="/static/js/bootstrap.min.js"></script>
<script type="text/javascript" src="/static/js/scripts.js"></script>
<script type="text/javascript" src="/static/js/pnotify.js"></script>
<script type="text/javascript" src="/static/js/pnotify.buttons.js"></script>

</body>
</html>

auth.vm — для відображення авторизованого користувача

<html>
<head>
 <meta charset="utf-8"/>
 <title>IoC Test</title>
 <link rel="stylesheet" href="/static/css/bootstrap.min.css">

 <link rel="stylesheet" href="/static/css/style.css"/>
 <link rel="stylesheet" href="/static/css/pnotify.custom.min.css"/>
 <link rel="stylesheet" href="/static/css/pnotify.css"/>
 <link rel="stylesheet" href="/static/css/pnotify.buttons.css"/>
</head>
<body>
<div class="container">
 <h1>Authorized page</h1>
<br>

 <h4>Test auth data</h4>
 <div id="auth_data">
#if($!account)
 <h4>Hello @$!account.username, You successfully authenticated!</h4>
<br>
 <button type="button" class="btn btn-success btn-logout">Logout!</button>
#end
</div>
</div>

<script type="text/javascript" src="/static/js/jquery.js"></script>
<script type="text/javascript" src="/static/js/bootstrap.min.js"></script>
<script type="text/javascript" src="/static/js/scripts.js"></script>
<script type="text/javascript" src="/static/js/pnotify.js"></script>
<script type="text/javascript" src="/static/js/pnotify.buttons.js"></script>

</body>
</html>

scripts.js — контролер, для відправки і отримання інформації запиту на сервер

$(function () {
 $(".btn-create").click(function () {
 var cooki = cookie();
 document.cookie = 'CSRF-TOKEN=' + cooki;

$.ajax({
 url: "/signup",
 data: $('#creation).serialize(),
 headers: {'X-CSRF-TOKEN': cooki},
 crossDomain: true,
 xhrFields: {
 withCredentials: true
},
 type: "POST"
 }).done(function (data) {
 switch (data.type) {
 case 'OK':
 new PNotify({
 title: 'Success',
 text: data.message,
 type: 'success',
 hide: false
});
break;
 case 'ERROR':
 new PNotify({
 title: 'Error',
 text: data.message,
 type: 'error',
 hide: false
});
break;
}
});
});

 $(".btn-auth").click(function () {
 var cooki = cookie();
 document.cookie = 'CSRF-TOKEN=' + cooki;

$.ajax({
 url: "/signin",
 data: $('#auth').serialize(),
 headers: {'X-CSRF-TOKEN': cooki},
 crossDomain: true,
 xhrFields: {
 withCredentials: true
},
 type: "POST"

 }).done(function (data) {
 switch (data.type) {
 case 'OK':
 new PNotify({
 title: 'Success',
 text: data.message,
 type: 'success',
 hide: false
});

 setTimeout(function () {
 window.location = "/loginPage";
 }, 5000);
break;
 case 'ERROR':
 new PNotify({
 title: 'Error',
 text: data.message,
 type: 'error',
 hide: false
});
break;
}
});
});

 $(".btn-logout").click(function () {
$.ajax({
 url: "/signout",
 crossDomain: true,
 xhrFields: {
 withCredentials: true
},
 type: "GET"
 }).done(function (data) {
 switch (data.type) {
 case 'OK':
 new PNotify({
 title: 'Success',
 text: 'Logouting...',
 type: 'success',
 hide: false
});

 setTimeout(function () {
 window.location = data.message;
 }, 5000);
break;
 case 'ERROR':
 new PNotify({
 title: 'Error',
 text: data.message,
 type: 'error',
 hide: false
});
break;
}
});
});
});

function cookie(a) {
 return a // if the placeholder was passed, return
 ? ( // a random number from 0 to 15
 a ^ // unless b is 8,
 Math.random() // in which case
 * 16 // a random number from
 >> a / 4 // 8 to 11
 ).toString(16) // in hexadecimal
 : ( // or otherwise a concatenated string:
 [1e7] + // 10000000 +
 -1e3 + // -1000 +
 -4e3 + // -4000 +
 -8e3 + // -80000000 +
 -1e11 // -100000000000,
 ).replace( // replacing
 /[018]/g, // zeroes, ones and with eights
 cookie // random hex digits
)
}

Компилим, запускаємо все що у нас вийшло.
Якщо все правильно, побачимо щось подібне в кінці завантаження:

Читайте також  Наскільки R швидкий для продуктива?

Лог

[21.10.18 22:29:51:990] INFO web.model.mapping.MappingContainer: Mapped method [/], method=[GET], to [public org.ioc.web.model.ModelAndView org.examples.webapp.mapping.MainMapping.index()]
[21.10.18 22:29:51:993] INFO web.model.mapping.MappingContainer: Mapped method [/signup], method=[POST], to [public org.examples.webapp.responces.IMessage org.examples.webapp.mapping.MainMapping.createUser(java.lang.String,java.lang.String,java.lang.String)]
[21.10.18 22:29:51:993] INFO web.model.mapping.MappingContainer: Mapped method [/signin], method=[POST], to [public org.examples.webapp.responces.IMessage org.examples.webapp.mapping.MainMapping.auth(org.ioc.web.model.http.Request,java.lang.String,java.lang.String)]
[21.10.18 22:29:51:993] INFO web.model.mapping.MappingContainer: Mapped method [/signout], method=[GET], to [public org.examples.webapp.responces.IMessage org.examples.webapp.mapping.MainMapping.signout(org.ioc.web.model.http.Request)]
[21.10.18 22:29:51:995] INFO web.model.mapping.MappingContainer: Mapped method [/loginPage], method=[GET], to [public org.ioc.web.model.ModelAndView org.examples.webapp.mapping.MainMapping.authenticated(org.examples.webapp.domain.entity.TblAccount)]
[21.10.18 22:29:51:997] INFO ioc.web.factory.HttpInitializerFactory: Http server started on port(s): 8081 (http)

Результат:
1) Головна сторінка

2) Реєстрація

3) Аутентфікація

4) Сторінка з результатом авторизації(редирект після правильного вводу логіна і пароля)

5) Очищення інформації авторизації з сесії і перенаправлення користувача на стартову сторінку

6) Спроба не авторизованого користувача потрапити на сторінку з інформацією аутентифікації сесії

 

Кінець

Проект розвивається, вітаються “контрибьюторы” і оригінальні ідеї, оскільки одному робити цей проект важкувато.
Репозиторій проекту, так само на репозиторії є приклади використання всього функціоналу в модулі ‘examples’, і як говориться, “Good luck, have fun”, всім дякую за увагу.

Степан Лютий

Обожнюю технології в сучасному світі. Хоча частенько і замислююся над тим, як далеко вони нас заведуть. Не те, щоб я прям і знаюся на ядрах, пікселях, коллайдерах і інших парсеках. Просто приходжу в захват від того, що може в творчому пориві вигадати людський розум.

You may also like...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *