Java простое веб-приложения. Логин и регистрация

от автора

Всем доброго времени суток.

Эта статья рассчитана для новичков в мире Web и Java. Мы рассмотрим стандартную архитектуру веб-приложения и сделаем небольшой проект с нуля.

Будем использовать Spring (Огромный фреймворк для веба), но по минимуму. Если не иметь предыдущего опыта использования и сразу пытаться использовать весь функционал, то не будет базового понимания, так как имеется перечень подводных камней.

Что будет делать приложение

Хочу представить короткий и в то же время полезный пример. Он минимально загружен и по итогу вы сможете использовать его как шаблон.

Думая, с чем люди часто должны сталкиваться при разработки веб-приложения, понял что реализация логина и регистрации будет достаточно показательной и полезной.

Исходный код вы сможете найти здесь.

Что будем использовать

  • любимую IDE (я использую IntelliJ IDEA)
  • не замутненную голову
  • Maven (сборщик проекта)
  • JDBC (звено между Java и СУБД)

Создание проекта

Создайте простой Maven проект. Делаем все с чистого листа, не добавляя никаких архетипов.
IDE должна сгенерировать такую структуру.

image

И так, что мы видим.

  • java — основное место где будет происходить "магия"
  • resources — место где хранятся наши ресурсы. (Это могут быть файлы, картинки и чего только душа пожелает, зачастую тут хранится front часть приложения)
  • test — место где принято писать тесты к приложению. (Вы можете с чистой совестью удалять эту папку, тестирование мы рассматривать не будем)
  • pom.xml — сердце Maven. В нем содержится информация о проекте, тут прописываются все зависимости, указывается какой тип "упаковки" должен быть у проекта и тд.

Кстати, не пропустите сообщения от IDE, и разрешите авто-импорт для Maven.

image

Архитектура веб-приложения

Чтобы начать кодить, нужно подкрепиться немного теорией.
Простите за мой дизайнерский талант, я в этом не силен.

image

На картинке показаны слои веб-приложения, каждый слой может взаимодействовать только со своими соседями, если приложение написано "по-правильному".

И так пробежимся по каждому слою и разберем их назначения.

  1. Browser — это лицо нашего приложения. То что видит пользователь и что называют Frontend. Он может взаимодействовать с Backend — серверной частью, а именно с контроллером. Этот слой должен уметь отправлять и получать данные от контроллера.
  2. Controller — должен контролировать запросы от Frontend, уметь их обрабатывать и возвращать данные. И, конечно же, задачей контролера является общение со следующим слоем.
  3. Service — в этом слое должна быть реализована бизнес-логика приложения. Можно считать этот слой корнем приложения. В нем происходят самые тяжеловесные операции.
  4. DAL(data access layer) — этот слой должен общаться с нашей БД. В нём реализованы все запросы к БД, которые нужны для приложения, а именно запросы на выборку, изменения и тд.
  5. Database — она же БД, структурированная туса данных.

Проектирование базы данных

Один из самых первых этапов разработки веб-приложения является проектирование БД.

Выбор БД предоставляется на ваше усмотрение, но у меня в примере будет использоваться H2.

Так как я хочу чтобы пример вышел небольшой, но чтобы в нем удалось рассмотреть как можно больше скользких моментов и понять всеобщие принципы построения веб-приложений. В примере будет только одна таблица User.

create table DATABASE.USER (     ID INT auto_increment,     NAME VARCHAR not null,     SURNAME VARCHAR not null,     LOGIN VARCHAR not null,     PASSWORD VARCHAR not null,     constraint USER_PK         primary key (ID) ); 

Добавляем Spring в проект

Вспоминаем про нашу папку java.

Создаем внутри java пакет com.zuk(вы можете называть иначе), в нем класс App с обычной main функцией.

И добавим щепотку магии Spring. Не будем вдаваться сильно в то что здесь происходит, с помощью @SpringBootApplication мы просто говорим Spring, чтобы он делал автоматическую настройку, чтобы он сканировал все что ему нужно. Сейчас рекомендую рассматривать Spring как автономную республику.

//com.zuk.App @SpringBootApplication public class App {      public static void main(String[] args) {         SpringApplication.run(App.class, args);     } }

Не впадаем в панику если все красное и не забываем подключать зависимости в pom.xml.

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          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>      <groupId>org.zuk</groupId>     <artifactId>LoginAndRegistration</artifactId>     <version>1.0-SNAPSHOT</version>      <properties>         <maven.compiler.source>1.8</maven.compiler.source>         <maven.compiler.target>1.8</maven.compiler.target>     </properties>      <dependencies>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>             <version>2.2.5.RELEASE</version>         </dependency>     </dependencies>  </project>

Если вы все сделали правильно у вас появится возможность сделать билд проекта, и Spring скажет вам привет.

image

Spring говорит что он стартанул сервер на порту 8080. Мы можем сами убедиться в этом, сходив на http://localhost:8080.

Подключаем БД к проекту

И так мы уже создали нашу Базу даных, подключили Spring к проекту, но они ещё не знают о существование друг друга.

Давайте создадим application.properties по пути src.main.resources.

Этот файл служит для хранения конфигурационных параметров в формате ключ-значение.

spring.datasource.driverClassName=org.h2.Driver spring.datasource.url=jdbc:h2:~/test  spring.datasource.username=sa spring.datasource.password=

Это стандартные ключи для Spring, и он сам может их прочитать.

Чтобы вы смогли убедиться в соединении, предлагаю проверить сможем ли мы подключиться в БД.
Хочу предупредить, моё решение будет спорным. На мой взгляд для новичка оно будет показательным и не будет вызывать много вопросов, но не стоит сильно опираться на данный подход. В Spring есть интересные реализации для работы с БД.
Советую почитать несколько статей по данной тематике.

По пути java.com.zuk.connection создадим класс ConnectionManager, он должен отдавать нам соединение с БД, и с этим соединением мы в дальнейшем будем работать.
Для этого нам понадобится вытянуть наши properties, сделаем это с помощью FileInputStream.

//ConnectionManager FileInputStream fis; Properties property = new Properties();

Проберемся к нашему application.properties, и вытянем с него properties.

//ConnectionManager fis = new FileInputStream("src/main/resources/application.properties"); property.load(fis);

Создаем метод getConnection.

  //ConnectionManager   public Connection getConnection() {             Connection connection = null;             try {                 connection = DriverManager.getConnection(property.getProperty("spring.datasource.url"),property.getProperty("spring.datasource.username"),property.getProperty("spring.datasource.password"));             } catch (SQLException e) {                 e.printStackTrace();             }             return connection;         }

Теперь можете проверить есть ли соединение, сделать это можно и в App.

Если у вас все получилось, должно вывести такое.

conn2: url=jdbc:h2:~/test user=SA

Ах да, не забываем добавить зависимость в наш любимый pom.

<dependency>     <groupId>com.h2database</groupId>     <artifactId>h2</artifactId>     <version>1.4.200</version> </dependency>

POJO (Plain Old Java Objects)

Не пугайтесь странных аббревиатур, я расскажу простыми словами зачем это нам надо, и что мы с этим будем делать.

Представьте простой класс, который ничего не наследует, в который не должно быть реализованы ничего сложно. Это просто класс, который служит носителем информации, в котором есть некоторые поля, геттеры и сеттеры для них. Максимум, что может быть, это реализация стандартных методов объекта. Это и есть наш POJO.

Для нашего проекта, это будет описание сущности таблицы User.
То есть мы реализуем таблицу в виде класса.

И так создаем пакет java.com.zuk.entity, в нем класс User.
Запишем в поля класса поля таблицы, и реализуем для каждого геттер и сеттер.

User.java

public class User {      int id;     String name;     String surname;     String login;     String password;      public int getId() {         return id;     }      public void setId(int id) {         this.id = id;     }      public String getName() {         return name;     }      public void setName(String name) {         this.name = name;     }      public String getSurname() {         return surname;     }      public void setSurname(String surname) {         this.surname = surname;     }      public String getLogin() {         return login;     }      public void setLogin(String login) {         this.login = login;     }      public String getPassword() {         return password;     }      public void setPassword(String password) {         this.password = password;     } }

DAL

Что же, самое время приступить к следующему слою, а именно DAL. Тут будут реализованы все запросы к нашей БД, которые нам понадобятся. Честь этого слоя будет отстаивать DAO (data access object) — интерфейс к какому-либо типу базы данных или механизму хранения.

Создаем пакет java.com.zuk.dao в нем интерфейс UserDao (принято сначала писать имя таблицы). Опишем пока что только два метода findByLogin, save.

public interface UserDao {     User findByLogin(User user);     Boolean save(User user); }

Дальше напишем реализацию для этого интерфейса, создаем пакет java.com.zuk.dao.impl,
в нем класс UserDaoImpl.
Вы можете спросить зачем так сильно разделять все — это будет служить хорошей привычкой, вдруг вам придется работать в большом проекте, там скорее всего будут специфические реализации (например, если используется несколько БД).

public class UserDaoImpl implements UserDao {     @Override     public User findByLogin(User user) {         return null;     }      @Override     public Boolean save(User user) {         return null;     } }

Начнем с findByLogin. Нам нужно сделать такой SQL запрос.

SELECT  * from DATABASE.User where LOGIN=login

Далее нужно вернуть объект класса User.

Проверим есть ли у нас соединение с базой, если же его не будет мы вернем null.

//UserDaoImpl ConnectionManager cm = new ConnectionManager(); Connection con = cm.getConnection();  @Override     public User findByLogin(String login) {         User user = null;         if (con != null) {               //..           }         return user;      }

Если условие в if правильное, мы выполним SQL запрос, и запишем значение в user.

//findByLogin PreparedStatement pr = con.prepareStatement("SELECT * FROM DATABASE.User where LOGIN=?"); pr.setString(1 , login); ResultSet resultSet = pr.executeQuery();//return sql result if(resultSet.next()) {        user = new User();        user.setId(resultSet.getInt("ID"));        user.setName(resultSet.getString("NAME"));        user.setSurname(resultSet.getString("SURNAME"));        user.setLogin(login);        user.setPassword(resultSet.getString("PASSWORD"));        return user; }  pr.close();  con.close();

Не забываем покрывать возможные исключения.

Попробуйте реализовать save своими руками.
Код оставлю под спойлером.

save

//UserDaoImpl @Override     public Boolean save(User user) {         ConnectionManager cm = new ConnectionManager();         Connection con = cm.getConnection();         if (con != null) {             try {                 PreparedStatement pr = con.prepareStatement("insert into DATABASE.USER (NAME,SURNAME,LOGIN,PASSWORD) values (?,?,?,?)");                 pr.setString(1,user.getName());                 pr.setString(2,user.getSurname());                 pr.setString(3,user.getLogin());                 pr.setString(4, DigestUtils.md5DigestAsHex((user.getPassword()).getBytes()));                 pr.executeUpdate();                 pr.close();                 con.close();                 return true;             } catch (SQLException e) {                 e.printStackTrace();              } catch (Exception e) {                 e.printStackTrace();             }         }         return false;     }

Service

В сервисе будет находится наша бизнес-логика. Создаем пакет java.com.zuk.service, в нем интерфейс UserService с двумя методами: login, registration. И создаем класс UserServiceImpl в java.com.zuk.service.impl, который имплементирует UserService.

public class UserServiceImpl implements UserService {     @Override     public String login(User user) {         return null;     }      @Override     public String registration(User user) {         return null;     } } 

Предлагаю начать с login.
Алгоритм будет таким:

  1. Проверим существует ли поле "login" в БД.
  2. Если условие 1 выполняется, проверяем совпадает ли пароль.
  3. Если условие 2 выполняется, скажем "life is beautiful"

//UserServiceImpl UserDaoImpl userDao = new UserDaoImpl();     @Override     public String login(User user) {         User findUser = userDao.findByLogin(user.getLogin());         if(findUser!=null){             if(DigestUtils.md5DigestAsHex((user.getPassword()).getBytes()).equals(findUser.getPassword())){                 return "life is beautiful";             }         }         return "do not give up";     }

Попытайтесь сделать регистрацию своими руками.

registration

 @Override     public String registration(User user) {         User findUser = userDao.findByLogin(user.getLogin());         if(findUser==null) {             userDao.save(user);             return "life is beautiful";         }         return "this login is not available";     }

Controller

Настала звездный час контроллера, и мы наконец-то сможем увидеть результат наших деяний.
Создаём пакет java.com.zuk.controller в нем класс Controller. Перед классом разместим "магическую" аннотацию RestController, она поможет Spring понять, что этот класс будет служить нам как Rest Controller.
Создаём метод, который возвращает строку и аннотируем его с помощью @RequestMapping("/") и @ResponseBody.

@RestController public class Controller {     @RequestMapping("/")     @ResponseBody     String main() {         return "Hello from Controller";     } }

Делаем билд проекта, и переходим на http://localhost:8080.
Мы должны увидеть "Hello from Controller".

Теперь создаём два html файла. Один будет с формой регистрации, второй с формой логина.
Правильным тоном будет сохранение этих файлы внутри resources, а еще лучше в папку resources.static.

html

<!--loginFrom.html--> <html> <title>Login</title> <body> <form action="http://localhost:8080/login">     <input type="text" required name="login" placeholder="login" />     <input type="password" required name="password" placeholder="password" />     <button>login</button> </form> <a href="http://localhost:8080/registrationForm">don't have account</a> </body> </html>

<!--registrationForm.html--> <html> <title>Registration</title> <body> <form action="http://localhost:8080/registration">     <input type="text" required name="name" placeholder="name" />     <input type="text" required name="surname" placeholder="surname" />     <input type="text" required name="login" placeholder="login" />     <input type="password" required name="password" placeholder="password" />     <button>registration</button> </form> <a href="http://localhost:8080/loginForm">already have account</a> </body> </html>

Теперь я хочу, чтобы при переходе по ссылке http://localhost:8080/loginForm, или http://localhost:8080/registrationForm выводились наши html файлы с формами.
Возвращаемся к нашему контроллеру, тут мы должны добавить мапинг для /loginForm, /registrationForm и отдавать соответствующую html. В этом нам поможет ModelAndView.

//Controller  @RequestMapping("/loginForm")     @ResponseBody     ModelAndView loginForm() {         ModelAndView modelAndView = new ModelAndView();         modelAndView.setViewName("loginForm.html");         return modelAndView;     }      @RequestMapping("/registrationForm")     @ResponseBody     ModelAndView registrationForm() {         ModelAndView modelAndView = new ModelAndView();         modelAndView.setViewName("registrationForm.html");         return modelAndView;     }

Если вы заметили, то в форме я уже написал action на http://localhost:8080/login и registration, но мы ещё не ловим эти переходы в контроллере.
Пора это исправлять и связаться с нашим service.

//Controller UserServiceImpl userService = new UserServiceImpl(); //..  @RequestMapping("/login")     @ResponseBody     String login(@RequestParam String login,@RequestParam String password) {         User user = new User();         user.setLogin(login);         user.setPassword(password);         return userService.login(user);     }      @RequestMapping("/registration")     @ResponseBody     String registration(@RequestParam String name,@RequestParam String surname, @RequestParam String login,@RequestParam String password) {         User user = new User();         user.setName(name);         user.setSurname(surname);         user.setLogin(login);         user.setPassword(password);         return userService.registration(user);     }

Мы видим, что наши методы в Controller начали принимать параметры. Это те параметры, которые мы пишем в наших формах. Их имена должны совпадать с именами input.

Заключение

Надеюсь эта статья смогла ответь на некоторые ваши вопросы, и помогла расширить свои горизонты.

Всем приятного развития.

ссылка на оригинал статьи https://habr.com/ru/post/495010/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *