[JAVA] La première application WEB avec Spring Boot-Making a Pomodoro timer-

Aperçu

Cet article est un record lorsque j'ai essayé de créer une application WEB à partir de zéro pour la première fois. Je n'ai pas été en mesure d'expliquer les détails techniques car il y a certaines parties que je ne comprends pas, mais depuis que j'ai posté le flux de la conception à la mise en œuvre et le code source, j'espère qu'il servira de référence pour d'autres débutants.

Terminé

Une application WEB qui enregistre l'heure de début du travail, affiche une alerte après 50 minutes et enregistre l'heure de fin du travail.

image.png

environnement

Livrables

Arborescence du projet


.
├── src
│   ├── main
│   │   ├── java
│   │   │   └── jp
│   │   │       └── co
│   │   │           └── anyplus
│   │   │               ├── Entity
│   │   │               │   └── TasksEntity.java
│   │   │               ├── Repository
│   │   │               │   └── TasksRepository.java
│   │   │               ├── ServletInitializer.java
│   │   │               ├── TaskTrackerApplication.java
│   │   │               ├── controller
│   │   │               │   └── IndexController.java
│   │   │               ├── form
│   │   │               │   └── TaskForm.java
│   │   │               └── service
│   │   │                   ├── TaskCancelService.java
│   │   │                   └── TaskRegisterService.java
│   │   ├── resources
│   │   │   ├── application.properties
│   │   │   ├── static
│   │   │   │   ├── css
│   │   │   │   │   ├── bootstrap-grid.css
│   │   │   │   │   ├── bootstrap-grid.css.map
│   │   │   │   │   ├── bootstrap-grid.min.css
│   │   │   │   │   ├── bootstrap-grid.min.css.map
│   │   │   │   │   ├── bootstrap-reboot.css
│   │   │   │   │   ├── bootstrap-reboot.css.map
│   │   │   │   │   ├── bootstrap-reboot.min.css
│   │   │   │   │   ├── bootstrap-reboot.min.css.map
│   │   │   │   │   ├── bootstrap.css
│   │   │   │   │   ├── bootstrap.css.map
│   │   │   │   │   ├── bootstrap.min.css
│   │   │   │   │   ├── bootstrap.min.css.map
│   │   │   │   │   └── jQuery.countdownTimer.css
│   │   │   │   ├── images
│   │   │   │   └── js
│   │   │   │       ├── bootstrap.bundle.js
│   │   │   │       ├── bootstrap.bundle.js.map
│   │   │   │       ├── bootstrap.bundle.min.js
│   │   │   │       ├── bootstrap.bundle.min.js.map
│   │   │   │       ├── bootstrap.js
│   │   │   │       ├── bootstrap.js.map
│   │   │   │       ├── bootstrap.min.js
│   │   │   │       ├── bootstrap.min.js.map
│   │   │   │       ├── import.js
│   │   │   │       ├── jQuery.countdownTimer.js
│   │   │   │       ├── jQuery.countdownTimer.min.js
│   │   │   │       ├── jQuery.countdownTimer.min.js.map
│   │   │   │       ├── jquery-3.4.1.min.js
│   │   │   │       ├── localisation
│   │   │   │       └── view-index.js
│   │   │   └── templates
│   │   │       └── index.html

conception

J'ai rendu la fonction aussi simple que possible et je l'ai faite en partant du principe qu'elle peut être réalisée en une journée pour le moment.

Design fonctionnel

// L'affichage des résultats a été reporté cette fois. Je veux le mettre en œuvre à l'avenir.

Le diagramme de séquence est le suivant.

image.png

Conception de table

Créez une seule table car vous n'avez besoin de stocker que les tâches que vous avez effectuées.

** Tableau des tâches **

colonne La description
task_id Valeur de séquence
task_name Nom de la tâche
task_category Catégorie de travail.
task_starttime Heure de début du travail
task_endtime Heure de fin de travail
userid IDENTIFIANT D'UTILISATEUR. Parce qu'il n'y a pas de fonction utilisateursystemEn supposant que seul peut être saisi

DB Nous avons préparé une base de données pour cette application WEB et créé le tableau comme suit.

DDL


CREATE TABLE public.tasks (
	task_id bigserial NOT NULL,
	task_name varchar(200) NULL,
	task_category varchar(200) NULL,
	task_starttime timestamp NULL,
	task_endtime timestamp NULL,
	userid varchar(20) NULL
);

Créer un projet

Créez un projet. Cette fois, je l'ai fait à partir du Spring Starter Project comme suit.

image.png

image.png

Le référentiel Maven défini ci-dessus est le suivant.

--SpringBootDevTool: Un outil de développement pratique qui se compile et redémarre automatiquement lorsqu'un changement de source Java est déclenché. --SpringDataJpa: une bibliothèque pratique pour les opérations DB. Il effectue une conversion automatique de type entre Java <-> DB. --PostgreSQL Driver: pilote pour accéder à PostgreSQL --Thymeleaf: moteur de modèle. Remplacez automatiquement la variable décrite par la valeur d'attribut par la valeur --SpringWebStarter: un ensemble de définitions qui inclut tous les paramètres et dépendances de bibliothèque requis pour une application WEB.

Après avoir créé le projet, cliquez avec le bouton droit sur le projet> Exécuter> Maven Install pour installer le fichier de référentiel défini dans pom.xml.

Dans cette procédure, pom.xml est généré automatiquement dans le projet Spring starter, Lors du changement de version ou de l'ajout manuel de la bibliothèque, il semble préférable d'éditer directement pom.xml et d'exécuter Maven Install ou Maven Clean and Install. Vous pouvez trouver le référentiel Maven sur ce site.

Ensuite, ouvrez le fichier application.properties et définissez les paramètres de connexion à la base de données.

application.properties


spring.datasource.url=jdbc:postgresql://localhost:5432/[Nom de la base de données]
spring.datasource.username=[Utilisateur de la base de données]
spring.datasource.password=[Mot de passe de la base de données]
spring.datasource.driver-class-name=org.postgresql.Driver

Exécutez-le pour voir si le projet est prêt sans aucun problème. (Faites un clic droit sur le projet> Exécuter> Application Spring Boot)

Confirmez que le message suivant est sorti dans le journal de la console et passez au suivant.

o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s):-(Abréviation)-
jp.co.anyplus.TaskTrackerApplication     : Started [Nom de l'application] in [Valeur numérique] seconds (JVM running for [Valeur numérique])

Implémentation de l'écran

Bibliothèque

Tout d'abord, mettez les bibliothèques requises. Il y avait jQuery en tant qu'extension de JavaScript, Bootstrap pour la mise en page et Countdown Timer pour la minuterie, alors j'ai utilisé ceci.

CountdownTimer (https://github.com/harshen/jQuery-countdownTimer/) Une bibliothèque jQuery qui a un ensemble complet de fonctions de décompte et de décompte de la date et de l'heure. C'était parfait pour cette application car elle a toutes les fonctions de spécification de mise en page et de démarrage / arrêt.

html Je n'ai fait qu'un seul écran cette fois.

La disposition est décidée par Bootstrap. La zone d'affichage de la minuterie est déterminée par <span id =" timerView ">, et l'emplacement d'affichage est spécifié pour les parties de la minuterie Coundown. Le CSS par défaut de CoundownTimer est un peu insatisfaisant, donc je le modifie, mais je vais l'omettre ici.

index.html


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="/css/jQuery.countdownTimer.css" />

<title>TaskTracker</title>
</head>
<body>
    <main class="container">
        <h1>Task Tracker</h1>
        <p>Enregistrez la tâche.</p>
        <div class="container text-center">
            <div id="taskMainContainer" class="jumbotron col-md-8 mx-auto">
                <form id="TaskForm" th:object="${taskForm}">
                    <div class="row">
                        <div class="col display-4"><span id="timerView">50:00</span></div>
                    </div>
                    <div class="row mt-3 justify-content-md-center">
                        <div class="col-md-4 "><button id="startButton" type="button" class="btn btn-block btn-success" disabled>Start</button></div>
                        <div class="col-md-4 "><button id="stopButton" type="button" class="btn btn-block btn-danger" disabled>Stop</button></div>
                    </div>
                    <div class="row mt-3 justify-content-md-center">
                        <div class="col-md-4 p-1"><input th:field="*{category}" type="text" class="form-control" placeholder="Catégorie"></div>
                        <div class="col-md-8 p-1"><input th:field="*{taskName}" type="text" class="form-control" placeholder="tâche"></div>
                        <div><input th:field="*{taskId}" type="text" class="form-control" hidden></div>
                    </div>
                </form>
            </div>
        </div>
    </main>
  
    <script src="/js/import.js"></script>
    <script src="/js/view-index.js"></script>
  </body>
</html>

JavaScript J'ai créé import.js pour importer la bibliothèque et view-index.js pour décrire le traitement d'écran.

import.js


/**
 * import
 **/
document.write('<script type="text/javascript" src="/js/jquery-3.4.1.min.js"></script>');
document.write('<script type="text/javascript" src="/js/bootstrap.bundle.min.js"></script>');

view-index.js


/*--------------------------------
 * index.JS pour html
 --------------------------------*/
document.write('<script type="text/javascript" src="/js/jQuery.countdownTimer.js"></script>');

$(function(){
    $("#timerView").countdowntimer({
        minutes :50,
        seconds :00,
        displayFormat : "MS",
        size : "xl",
        timeUp : taskFinish
    });
    $("#timerView").countdowntimer("stop", "stop");
    stopbtnOn();
});

/*--------------------------------
 *Lancer la tâche
 --------------------------------*/
 $('#startButton').on('click', taskStart);
 function taskStart(){
    startbtnOn();
    $("#timerView").countdowntimer("stop", "start");

    var form = $('#TaskForm').serializeArray();
    var formdata = {};
    jQuery.each(form, function(i, e) {
    	formdata[e.name] = e.value;
    });
    
    $.ajax({
        url:'/',
        type:'POST',
        contentType : 'application/json; charset=utf-8',
        data: JSON.stringify(formdata)
    }).done( (data) => {
        console.log("success");
        console.log(data);
        $('#taskId').val(data);
    }).fail( (data) => {
        console.log("fail");
        console.log(data);
    });
}

/*--------------------------------
 *Arrêter la tâche
 --------------------------------*/
$('#stopButton').on('click', taskStop);
function taskStop(){
    stopbtnOn();
    $("#timerView").countdowntimer("stop", "stop");

    var form = $('#TaskForm').serializeArray();
    var formdata = {};
    jQuery.each(form, function(i, e) {
    	formdata[e.name] = e.value;
    });
    
    $.ajax({
        url:'/stop',
        type:'POST',
        contentType : 'application/json; charset=utf-8',
        data: JSON.stringify(formdata)
    }).done( (data) => {
        console.log("success");
        console.log(data);
    }).fail( (data) => {
        console.log("fail");
        console.log(data);
    });
}

/*--------------------------------
 *Tâche terminée
 --------------------------------*/
function taskFinish(){
    setTimeout(() => {
        taskStop();
        alert("Finish!!");
        $("#timerView").countdowntimer("stop", "stop");
        stopbtnOn();
    }, 1000);
}

/*--------------------------------
 *Contrôle du bouton: lorsque le bouton de démarrage est activé
 --------------------------------*/
function startbtnOn(){
    $('#startButton').prop("disabled", true);
    $('#stopButton').prop("disabled", false);
}

/*--------------------------------
 *Contrôle du bouton: lorsque le bouton d'arrêt est activé
 --------------------------------*/
function stopbtnOn(){
    $('#startButton').prop("disabled", false);
    $('#stopButton').prop("disabled", true);
}

Implémentation côté serveur

Classe de couche de modèle

Commencez par créer une classe TasksEntity pour stocker les données de table et une classe TasksRepository pour manipuler la table créée dans la base de données.

TasksEntity.java


package jp.co.anyplus.Entity;

import java.sql.Timestamp;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="tasks")
public class TasksEntity {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name="task_id")
	private Long taskId;
	@Column(name="task_name")
	private String taskName;
	@Column(name="task_category")
	private String taskCategory;
	@Column(name="task_starttime")
	private Timestamp taskStartTime;
	@Column(name="task_endtime")
	private Timestamp taskEndTime;
	@Column(name="userid")
	private String userid;
	
	public Long getTaskId() {
		return taskId;
	}
	public void setTaskId(Long taskId) {
		this.taskId = taskId;
	}
	public String getTaskName() {
		return taskName;
	}
	public void setTaskName(String taskName) {
		this.taskName = taskName;
	}
	public String getTaskCategory() {
		return taskCategory;
	}
	public void setTaskCategory(String taskCategory) {
		this.taskCategory = taskCategory;
	}
	public Timestamp getTaskStartTime() {
		return taskStartTime;
	}
	public void setTaskStartTime(Timestamp taskStartTime) {
		this.taskStartTime = taskStartTime;
	}
	public Timestamp getTaskEndTime() {
		return taskEndTime;
	}
	public void setTaskEndTime(Timestamp taskEndTime) {
		this.taskEndTime = taskEndTime;
	}
	public String getUserid() {
		return userid;
	}
	public void setUserid(String userid) {
		this.userid = userid;
	}
}

TasksRepository.java


package jp.co.anyplus.Repository;

import java.sql.Timestamp;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import jp.co.anyplus.Entity.TasksEntity;

@Repository
public interface TasksRepository extends JpaRepository<TasksEntity, Long> {

	@Query(value="select current_timestamp", nativeQuery = true)
	public Timestamp getCurrentTime();
	
	@Query(value="select * from tasks where task_id = :taskId"
			, nativeQuery = true)
	public TasksEntity getByTaskId(@Param("taskId")Long taskId);
}

JpaRepository hérité de TasksRepository.java peut utiliser la fonction de Spring Data JPA (Article de référence). Il semble que le contenu de cette requête puisse être généré automatiquement à partir du nom de la méthode sans écrire de SQL.

Couche contrôleur et classes liées à l'entreprise

Créez autour de la couche Controller pour passer de la vue au modèle. IndexController.java de la classe Cotroller, TaskRegisterService.java et TaskCancelService.java de la classe Service, Écran <-> Nous avons préparé un total de 4 classes de TaskForm.java pour transmettre les informations de tâche entre les serveurs.

IndexController.java


//Instructions de package et d'importation omises

@Controller
public class IndexController {

	@Autowired
	TaskRegisterService registerService;
	@Autowired
	TaskCancelService cancelService;

	/**
	 *Traitement d'affichage initial
	 * 
	 * @param mav ModelAndView
	 * @retour Informations d'affichage initial
	 */
	@GetMapping("/")
	public ModelAndView init(ModelAndView mav) {
		TaskForm taskForm = new TaskForm();
		
		mav.addObject("taskForm", taskForm);
		mav.setViewName("index.html");
		return mav;
	}

	/**
	 *Processus d'enregistrement des tâches
	 * 
	 * @param model Model
	 * @param taskForm Formulaire d'informations sur les tâches
	 * @return ID de tâche nouvellement attribué
	 */
	@PostMapping("/")
	@ResponseBody
	public String taskRegister(Model model, @RequestBody TaskForm taskForm) {

		try {

			TasksEntity regTask = registerService.registerTask(taskForm);
			
			return regTask.getTaskId().toString();

		} catch (Exception e) {
			e.printStackTrace();
			return "";
		}
	}
	
	/**
	 *Traitement de fin de tâche
	 * 
	 * @param model Model
	 * @param taskForm Formulaire d'informations sur les tâches
	 * @retour du message de traitement
	 */
	@PostMapping("/stop")
	@ResponseBody
	public String taskEnd(Model model, @RequestBody TaskForm taskForm) {
		
		try {
			cancelService.endTask(taskForm);
		} catch(RuntimeException e) {
			e.printStackTrace();
			return "error";
		}
		
		return "task canceling success";
	}

}

Je n'ai pas compris les classes @ResponseBody, @ PostMapping / @ GetMapping et Model utilisées pour communiquer entre l'écran et le serveur. Si vous comprenez, j'aimerais revoir la source et faire un article sur Qiita.

TaskRegisterService.java


//Instructions de package et d'importation omises

@Service
public class TaskRegisterService {

	@Autowired
	TasksRepository tasksRep;

	@Transactional(rollbackOn = Exception.class)
	public TasksEntity registerTask(TaskForm taskForm) throws RuntimeException {
		Timestamp currenttime = tasksRep.getCurrentTime();

		TasksEntity taskEntity = new TasksEntity();
		taskEntity.setTaskName(taskForm.getTaskName());
		taskEntity.setTaskCategory(taskForm.getCategory());
		taskEntity.setTaskStartTime(currenttime);
		taskEntity.setUserid("system");

		try {
			TasksEntity regTask = tasksRep.saveAndFlush(taskEntity);
			return regTask;
		} catch (RuntimeException e) {
			//log: l'enregistrement a échoué.
			throw e;
		}
	}

}

TaskCancelService.java


//Instructions de package et d'importation omises

@Service
public class TaskCancelService {

	@Autowired
	TasksRepository tasksRep;

	@Transactional(rollbackOn = Exception.class)
	public void endTask(TaskForm taskForm) throws RuntimeException {
		Timestamp currenttime = tasksRep.getCurrentTime();

		TasksEntity taskEntity = tasksRep.getByTaskId(Long.parseLong(taskForm.getTaskId()));
		
		taskEntity.setTaskId(Long.parseLong(taskForm.getTaskId()));
		taskEntity.setTaskEndTime(currenttime);

		try {
			tasksRep.saveAndFlush(taskEntity);
		} catch (RuntimeException e) {
			//log: échec de la mise à jour.
			throw e;
		}
	}

}

TaskForm.java


//Instructions de package et d'importation omises

@SuppressWarnings("serial")
public class TaskForm implements java.io.Serializable {

	private String taskId;
	private String category;
	private String taskName;
	
	public String getTaskId() {
		return taskId;
	}
	public void setTaskId(String taskId) {
		this.taskId = taskId;
	}
	public String getCategory() {
		return category;
	}
	public void setCategory(String category) {
		this.category = category;
	}
	public String getTaskName() {
		return taskName;
	}
	public void setTaskName(String taskName) {
		this.taskName = taskName;
	}
	
}

Recommended Posts

La première application WEB avec Spring Boot-Making a Pomodoro timer-
Créer une application Web avec Javalin
Créons une application Web de gestion de livres avec Spring Boot part1
Créons une application Web de gestion de livres avec Spring Boot part3
Créons une application Web de gestion de livres avec Spring Boot part2
Créez une application Web simple avec Dropwizard
Démarrez le développement d'applications Web avec Spring Boot
Exécutez l'application WEB avec Spring Boot + Thymeleaf
Créer un serveur API Web avec Spring Boot
Construisez un système WEB avec Spring + Doma + H2DB
Connectez-vous aux applications Web Spring Boot sur la plate-forme Microsoft ID
J'ai essayé de cloner une application Web pleine de bugs avec Spring Boot
Construisez un système WEB avec Spring + Doma + H2DB + Thymeleaf
À peu près le flux de développement d'applications Web avec Rails.
Construisez un système WEB avec Spring + Doma + H2DB Partie 2
Une histoire remplie des bases de Spring Boot (résolu)
[Spring Boot] Création d'applications Web
Vérifier le fonctionnement de deux rôles avec une application de chat
Créez une application Web Hello World avec Spring Framework + Jetty
Jusqu'à ce que vous créiez une application Web avec Servlet / JSP (Partie 1)
Application Web construite avec docker (1)
Modéliser Digimon avec DDD pour la première fois Partie 1
AWS Elastic Beanstalk # 1 avec Java à partir de zéro - Création d'un environnement d'application Web Java à l'aide de l'interface de ligne de commande EB-
Implémentez un serveur API Web REST simple avec Spring Boot + MySQL
Créez un serveur Web simple avec la bibliothèque standard Java com.sun.net.httpserver
L'histoire du refactoring avec un assistant personnel pour la première fois dans une application Rails
Lisez le fichier sous le chemin de classe sous forme de chaîne de caractères avec ressort
[Probablement le plus simple] Développement d'applications WEB avec Apache Tomcat + Java Servlet
Quelle est la différence entre un serveur Web et un serveur d'applications?
Une histoire qui a eu du mal avec l'introduction de Web Apple Pay
Créer une application d'enquête avec Spring Boot
Créer une application de minuterie avec de la boue
Mémo après le premier projet Spring-MVC-
Spring AOP pour la première fois
Mémo après le premier projet Spring-Database-
[Spring Boot] Précautions lors du développement d'une application Web avec Spring Boot et du placement d'une guerre sur un serveur Tomcat indépendant
Créez un projet de développement d'application Spring Boot avec la commande cURL + tar
Comment vérifier avant d'envoyer un message au serveur avec Spring Integration
Développement d'applications Web Spring Boot2 avec connexion Visual Studio Code SQL Server
Développement d'applications Web Spring5 MVC avec connexion Visual Studio Code SQL Server
Création d'un environnement de développement pour les applications Web Java avec Docker pour Mac Part1
Un débutant Java a essayé de créer une application Web simple à l'aide de Spring Boot
Développement d'applications Web Spring Boot2 avec création de Visual Studio Code Hello World
Déployer automatiquement des applications Web développées en Java à l'aide de Jenkins [Spring Boot App Edition]
WAR l'application WEB par Spring Boot et la déployer sur le serveur Tomcat
Développement d'applications Web Spring5 MVC avec création de modèles Visual Studio Code Maven
[Java] Déployer une application Web créée avec Eclipse + Maven + Ontology avec Heroku
Créer un environnement de développement d'applications Web Java avec Docker pour Mac Part2
Envoyez une demande au backend après une authentification unique avec Spring Cloud Gateway
Traitement lors du démarrage d'une application avec Spring Boot
Créez un fichier jar avec la commande
Mémo après le premier projet Spring-What is Spring-
Lancez l'application Nginx + Spring Boot avec docker-compose
Exécutez DMN à l'aide du moteur Camunda DMN
Application CI / CD Spring avec CircleCI (Heroku)
Faisons une application de calculatrice avec Java ~ Créez une zone d'affichage dans la fenêtre
Développement d'applications Web Spring 5 MVC avec Visual Studio Code Utilisation de Spring Security 2/3 [Création de page 1/2]