Пишемо з 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. опціонально;
Анотація @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
)
}
Компилим, запускаємо все що у нас вийшло.
Якщо все правильно, побачимо щось подібне в кінці завантаження:
Лог
[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”, всім дякую за увагу.