Technology
- springboot
- security
- thymeleaf
- data jpa
- hibernate
- mysql
- validation
1.プロジェクトを作成する
① IntelliJ でプロジェクトを作成する
Project name : Ecommerce_springboot
JDK : 17 version 17.0.8
Maven
②spring initializrでLibraryプロジェクトを作成する
Java 17
jar
Group: com.ecommerce.library
Artifact: Library
Name : Library
Description: Demo project for Spring Boot
Package name: com.example.library.Library
- lombok
- Spring Security
- OAuth2 Client
- Spring Data JPA
- MySQL Driver
- Validation
- Java Mail Sender
③spring initializrでAdminプロジェクトを作成する
Java 17
jar
Group: com.ecommerce.admin
Artifact: Admin
Name : Admin
Description: Demo project for Spring Boot
Package name: com.example.admin.Admin
- Spring Boot DevTools
- lombok
- Spring Web
- Thymeleaf
- Spring Security
- OAuth2 Client
- Spring Data JPA
- MySQL Driver
- Vlidation
- Java Mail Sender
④src/pom.xmlにコードを追加する
//省略
<modules>
<module>Library</module>
<module>Admin</module>
</modules>
⑤Library/src/main/java/com.ecommerce.libraryにmodelとrepositoryとserviceとdtoとutilsフォルダを作成する
2.Libraryを編集する
①Library/src/main/java/com.ecommerce.library/modelにRole.javaを作成する
package com.example.library.model;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data @NoArgsConstructor @AllArgsConstructor
@Entity
@Table(name = "roles")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "role_id")
private Long id;
private String name;
}
②Library/src/main/java/com.ecommerce.library/modelにAdmin.javaを作成する
package com.example.library.model;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Collection;
@Data @NoArgsConstructor @AllArgsConstructor
@Entity
@Table(name = "admin")
public class Admin {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "admin_id")
private Long id;
private String firstName;
private String lastName;
private String username;
private String password;
@Lob
@Column(columnDefinition = "MEDIUMBLOB")
private String image;
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinTable(name = "admins_roles", joinColumns = @JoinColumn(name = "admin_id", referencedColumnName = "admin_id"),
inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "role_id"))
private Collection<Role> roles;
}
③Library/src/main/java/com.ecommerce.library/repositoryにRoleRepository.javaを作成する
package com.example.library.repository;
import com.example.library.model.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
Role findByName(String name);
}
④Library/src/main/java/com.ecommerce.library/repositoryにRoleRepository.javaを作成する
package com.example.library.repository;
import com.example.library.model.Admin;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
@Repository
public interface AdminRepository extends JpaRepository<Admin, Long> {
Admin findByUsername(String username);
}
⑤Library/src/main/java/com.ecommerce.library/dtoにAdiminDto.javaを作成する
package com.example.library.dto;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
@Data @NoArgsConstructor @AllArgsConstructor
public class AdminDto {
@Size(min = 3, max = 10, message = "Invalid first name!(3-10 characters)")
private String firstName;
@Size(min = 3, max = 10, message = "Invalid first name!(3-10 characters)")
private String lastName;
@Size(min = 3, max = 15, message = "Invalid password!(5-15 characters)")
private String username;
private String password;
private String repeatPassword;
}
⑥Library/src/main/java/com.ecommerce.library/serviceにAdiminService.javaを作成する
package com.example.library.service;
import com.example.library.dto.AdminDto;
import com.example.library.model.Admin;
public interface AdminService {
Admin findByUsername(String username);
Admin save(AdminDto adminDto);
}
⑦Library/src/main/java/com.ecommerce.library/serviceにimplフォルダを作成し、AdiminServiceImpl.javaを作成する
package com.example.library.service.imple;
import com.example.library.dto.AdminDto;
import com.example.library.model.Admin;
import com.example.library.repository.AdminRepository;
import com.example.library.repository.RoleRepository;
import com.example.library.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Arrays;
@Service
public class AdminServiceImpl implements AdminService {
@Autowired
private AdminRepository adminRepository;
@Autowired
private RoleRepository roleRepository;
@Override
public Admin findByUsername(String username){
return adminRepository.findByUsername(username);
}
@Override
public Admin save(AdminDto adminDto){
Admin admin = new Admin();
admin.setFirstName(adminDto.getFirstName());
admin.setLastName(adminDto.getLastName());
admin.setUsername(adminDto.getUsername());
admin.setPassword(adminDto.getPassword());
admin.setRoles(Arrays.asList(roleRepository.findByName("ADMIN")));
return adminRepository.save(admin);
}
}
⑧Admin/src/java/com.example.admin/AdimnApplicationをLibraryを受け取るように修正する
package com.example.admin.Admin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootApplication(scanBasePackages = "com.example.library.*")
@EnableJpaRepositories(value = "com.example.library.repository")
@EntityScan(value = "com.example.library.model")
public class AdminApplication {
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, args);
}
}
⑨Admin/pom.xmlにコードを追加する
//省略
<dependency>
<groupId>com.example.library</groupId>
<artifactId>Library</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
⑩右にある『maven』のボタンを押し、左上タブの🔁を押す
⑪Admin/resouces/application.propertiesを編集する
server.port=8019
server.servlet.context-path=/admin
spring.thymeleaf.check-template-location=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=LEGACYHTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
spring.thymeleaf.cache=false
spring.datasource.url=jdbc:mysql://localhost:3306/ecommerce_springboot
spring.datasource.username=root
spring.datasource.password=thanhhanu4681
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.generate-ddl=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=update
spring.servlet.multipart.enabled=true
spring.servlet.multipart.file-size-threshold=2kB
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=100MB
3. Admin Module に Spring Security を構成する
①Admin/src/java/com.example.adminにconfigフォルダを作成し、AdminDetails.javaを作成する
import com.example.library.model.Admin;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class AdminDetails implements UserDetails {
private Admin admin;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authories = new ArrayList<>();
for(Role role: admin.getRoles()){
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authories;
}
@Override
public String getPassword() {
return admin.getPassword();
}
@Override
public String getUsername() {
return admin.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
②Admin/src/java/com.example.admin/configにAdminServiceConfig.javaを作成する
package com.example.admin.config;
import com.example.library.model.Admin;
import com.example.library.repository.AdminRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.stream.Collectors;
public class AdminServiceConfig implements UserDetailsService {
@Autowired
private AdminRepository adminRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Admin admin = adminRepository.findByUsername(username);
if(admin == null){
throw new UsernameNotFoundException("Could not find username");
}
return new User(
admin.getUsername(),
admin.getPassword(),
admin.getRoles()
.stream()
.map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList()));
}
}
③Admin/src/java/com.example.admin/configにAdminConfiguration.javaを作成する
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
public class AdminConfiguration extends WebSecurityConfigurerAdapter {
@Bean
public UserDetailsService userDetailsService(){
return new AdminServiceConfig();
}
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider(){
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService());
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
@Override
protected void configure(AuthenticationMagagerBuilder auth) throws Exception{
auth.authenticationProvider(daoAuthenticationProvider());
}
@Override
protected void configure(HttpSecurity htto) throws Exception{
http.authorizeRequests().authMatchers("/*").permitAll()
.antMatcher("/admin/*")
.hasAuthority("ADMIN")
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/do-login")
.defaultSuccessUrl("/admin/index")
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutRequestMacther(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.permitAll();
}
}
④Admin/src/java/com.example.adminにcontrollerフォルダを作成し、LoginController.javaを作成する
package com.example.admin.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
@GetMapping("/login")
public String loginForm(){
return "login";
}
}
⑤Admin/src/main/resources/templatesにlogin.htmlを作成し、BoostrapのCSSとFormsのコードを加える
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>
<body>
<form>
<div class="mb-3">
<label for="exampleInputEmail1" class="form-label">Email address</label>
<input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp">
<div id="emailHelp" class="form-text">We'll never share your email with anyone else.</div>
</div>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label">Password</label>
<input type="password" class="form-control" id="exampleInputPassword1">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</body>
</html>
3.ログインと登録の検証
①theme wagonのSB Admin 2 – HTML5 Responsive Free Admin Dashboard Templateをダウンロードする
②Admin/src/main/resources/staticに①のcss dist img js less pages scss
vendorを加える
③Admin/src/main/resources/templatesにforget-password.html register.htmlを加える
④ Admin/src/main/resources/templates/forget-password.htmlを編集する
<!DOCTYPE html>
<html lang="en" xmlns:th = "https://www.thymeleaf.org/">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>SB Admin 2 - Forgot Password</title>
<!-- Custom fonts for this template-->
<link th:href="@{vendor/fontawesome-free/css/all.min.css}" rel="stylesheet" type="text/css">
<link
href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i"
rel="stylesheet">
<!-- Custom styles for this template-->
<link th:href="@{css/sb-admin-2.min.css}" rel="stylesheet">
</head>
<body class="bg-gradient-primary">
<div class="container">
<!-- Outer Row -->
<div class="row justify-content-center">
<div class="col-xl-10 col-lg-12 col-md-9">
<div class="card o-hidden border-0 shadow-lg my-5">
<div class="card-body p-0">
<!-- Nested Row within Card Body -->
<div class="row">
<div class="col-lg-6 d-none d-lg-block bg-password-image"></div>
<div class="col-lg-6">
<div class="p-5">
<div class="text-center">
<h1 class="h4 text-gray-900 mb-2">Forgot Your Password?</h1>
<p class="mb-4">We get it, stuff happens. Just enter your email address below
and we'll send you a link to reset your password!</p>
</div>
<form class="user">
<div class="form-group">
<input type="email" class="form-control form-control-user"
id="exampleInputEmail" aria-describedby="emailHelp"
placeholder="Enter Email Address...">
</div>
<a href="login.html" class="btn btn-primary btn-user btn-block">
Reset Password
</a>
</form>
<hr>
<div class="text-center">
<a class="small" href="register.html">Create an Account!</a>
</div>
<div class="text-center">
<a class="small" href="login.html">Already have an account? Login!</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript-->
<script th:src="@{vendor/jquery/jquery.min.js}"></script>
<script th:src="@{vendor/bootstrap/js/bootstrap.bundle.min.js}"></script>
<!-- Core plugin JavaScript-->
<script th:src="{@vendor/jquery-easing/jquery.easing.min.js}"></script>
<!-- Custom scripts for all pages-->
<script th:src="@{js/sb-admin-2.min.js}"></script>
</body>
</html>
⑤Admin/src/java/com.example.admin/controller/LoginController.javaを作成する
package com.example.admin.controller;
import com.example.library.dto.AdminDto;
import com.example.library.service.imple.AdminServiceImpl;
import jakarta.validation.Valid;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
public class LoginController {
@Autowired
private AdminServiceImpl adminService;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@GetMapping("/login")
public String loginForm(){
return "login";
}
@GetMapping("/register")
public String register(Model model) {
model.addAllAttributes("adminDto", new AdminDto());
return "register";
}
@GetMapping("/forgot-password")
public String forgotPassword(Model model) {
return "forgot-password";
}
@PostMapping("/register-new")
public String addNewAdmin(@Valid @ModelAttribute("adminDto")AdminDto adminDto,
BindingResult result,
Model model,
HttpSession session){
try{
session.removeAttribute("message");
if(result.hasErrors()){
model.addAttribute("adminDto", adminDto);
result.toString();
return "reqister";
}
String username = adminDto.getUsername();
Admin admin = adminService.findByUsername(username);
if(admin != null){
model.addAttribute("adminDto", adminDto);
redirectAttributes.addFlashAttribute("message","Your email has been registered!");
System.out.println("admin not null");
session.setAttribute("message","Your email has been registered!");
return "register";
}
if(adminDto = getPassword().equals(adminDto.getRepeatPassword())) {
adminDto.setPassword(passwordEncoder.encode(adminDto.getPassword()));
adminService.save(adminDto);
System.out.println("success");
session.setAttribute("message","Register successfully!");
model.addAttribute("adminDto", adminDto);
}else{
model.addAttribute("adminDto", adminDto);
session.setAttribute("message","Password is not same!");
System.out.println("password not same");
return "register";
}
} catch (Exception e) {
e.printStackTrace();
session.setAttribute("message","Server is error, please try again later!");
}
return "reqister";
}
⑥Admin/src/main/resources/templates/login.htmlを編集する
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org/">
<head>
<meta charset="UTF-8">
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Register</title>
<link href="vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
<link
href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,500,500i,600,600i,700,700i,800,800i,900,900i"
rel="stylesheet">
</head>
<body class="bg-gradient-primary">
<div class="container">
<!-- Outer Row -->
<div class="row justify-content-center">
<div class="col-xl-10 col-lg-12 col-md-9">
<div class="card o-hidden border-0 shadow-lg my-5">
<div class="card-body p-0">
<!-- Nested Row within Card Body -->
<div class="row">
<div class="col-lg-6 d-none d-lg-block bg-login-image"></div>
<div class="col-lg-6">
<div class="p-5">
<div class="text-center">
<h1 class="h4 text-gray-900 mb-4">Welcome Back!</h1>
</div>
<form class="user">
<div class="form-group">
<input type="email" class="form-control form-control-user"
id="exampleInputEmail" aria-describedby="emailHelp"
placeholder="Enter Email Address...">
</div>
<div class="form-group">
<input type="password" class="form-control form-control-user"
id="exampleInputPassword" placeholder="Password">
</div>
<div class="form-group">
<div class="custom-control custom-checkbox small">
<input type="checkbox" class="custom-control-input" id="customCheck">
<label class="custom-control-label" for="customCheck">Remember
Me</label>
</div>
</div>
<a href="index.html" class="btn btn-primary btn-user btn-block">
Login
</a>
<hr>
</form>
<hr>
<div class="text-center">
<a class="small" th:href="@{/forgot-password}">Forgot Password?</a>
</div>
<div class="text-center">
<a class="small" th:href="@{/register}">Create an Account!</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript-->
<script src="vendor/jquery/jquery.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Core plugin JavaScript-->
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
<!-- Custom scripts for all pages-->
<script src="js/sb-admin-2.min.js"></script>
</body>
⑦Admin/src/main/resources/templates/register.htmlを編集する
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org/" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Register</title>
<!-- Custom fonts for this template-->
<link href="vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
<link
href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i"
rel="stylesheet">
<!-- Custom styles for this template-->
<link href="css/sb-admin-2.min.css" rel="stylesheet">
</head>
<body class="bg-gradient-primary">
<div class="container">
<div class="card o-hidden border-0 shadow-lg my-5">
<div class="card-body p-0">
<!-- Nested Row within Card Body -->
<div class="row">
<div class="col-lg-5 d-none d-lg-block bg-register-image"></div>
<div class="col-lg-7">
<div class="p-5">
<div class="text-center">
<h1 class="h4 text-gray-900 mb-4">Create an Account!</h1>
</div>
<div th:if="${session.message}">
<p class="alert alert-danger" th:text="${session.message}"></p>
</div>
<form th:action="@{/register-new}" th:object="${adminDto}" method="POST">
<div class="form-group row">
<div class="col-sm-6 mb-3 mb-sm-0">
<input type="text" class="form-control form-control-user"
th:field = "*{firstName}"
id="exampleFirstName"
placeholder="First Name">
<div class="alert alert-danger" th:if="${#field.hasErrors('firstName')}"
th:errors="*{firstName}"></div>
</div>
<div class="col-sm-6">
<input type="text" th:field = "*{lastName}"
class="form-control form-control-user"
id="exampleLastName"
placeholder="Last Name">
<div class="alert alert-danger" th:if="${#field.hasErrors('lastName')}"
th:errors="*{lastName}"></div>
</div>
</div>
<div class="form-group">
<input type="email" th:field = "*{username}" class="form-control form-control-user" id="exampleInputEmail"
placeholder="Email Username">
</div>
<div class="form-group row">
<div class="col-sm-6 mb-3 mb-sm-0">
<input type="password" th:field = "*{password}" class="form-control form-control-user"
id="exampleInputPassword" placeholder="Password">
<div class="alert alert-danger" th:if="${#field.hasErrors('Password')}"
th:errors="*{Password}"></div>
</div>
</div>
<div class="col-sm-6">
<input type="password" th:field = "*{repeatPassword}" class="form-control form-control-user"
id="exampleRepeatPassword" placeholder="Repeat Password">
</div>
</div>
<button type="submit" class="btn btn-primary btn-user btn-block">
Register Account
</button>
<hr>
</form>
<hr>
<div class="text-center">
<a class="small" th:href = "@{/forgot-password}" href="forgot-password.html">Forgot Password?</a>
</div>
<div class="text-center">
<a class="small" th:href = "@{/login}" href="login.html">Already have an account? Login!</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript-->
<script src="vendor/jquery/jquery.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Core plugin JavaScript-->
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
<!-- Custom scripts for all pages-->
<script src="js/sb-admin-2.min.js"></script>
</body>
</html>
⑧Library/src/main/java/com.ecommerce.library/serviceにimplフォルダを作成し、AdiminServiceImpl.javaを編集する
package com.example.library.service.imple;
import com.example.library.dto.AdminDto;
import com.example.library.model.Admin;
import com.example.library.repository.AdminRepository;
import com.example.library.repository.RoleRepository;
import com.example.library.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Arrays;
@Service
public class AdminServiceImpl implements AdminService {
@Autowired
private AdminRepository adminRepository;
@Autowired
private RoleRepository roleRepository;
@Override
public Admin findByUsername(String username){
return adminRepository.findByUsername(username);
}
@Override
public Admin save(AdminDto adminDto){
Admin admin = new Admin();
admin.setFirstName(adminDto.getFirstName());
admin.setLastName(adminDto.getLastName());
admin.setUsername(adminDto.getUsername());
admin.setPassword(adminDto.getPassword());
admin.setRoles(Arrays.asList(roleRepository.findByName("ADMIN")));
return adminRepository.save(admin);
}
}
4.関数の登録(完了)、Thymeleafのフラグメント
①Admin/src/java/com.example.admin/controller/LoginController.javaを編集する
package com.example.admin.controller;
import com.example.library.dto.AdminDto;
import com.example.library.service.imple.AdminServiceImpl;
import jakarta.validation.Valid;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
public class LoginController {
@Autowired
private AdminServiceImpl adminService;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@GetMapping("/login")
public String loginForm(Model model){
model.addAttribute("title", "Login");
return "login";
}
@RequestMapping("/index")
public String home(Model model){
model.addAttribute("title", "Home Pages");
return "index";
}
@GetMapping("/register")
public String register(Model model) {
model.addAttribute("title", "Register");
model.addAllAttribute("adminDto", new AdminDto());
return "register";
}
@GetMapping("/forgot-password")
public String forgotPassword(Model model) {
model.addAttribute("title", "Forgot Password");
return "forgot-password";
}
@PostMapping("/register-new")
public String addNewAdmin(@Valid @ModelAttribute("adminDto")AdminDto adminDto,
BindingResult result,
Model model){
try{
if(result.hasErrors()){
model.addAttribute("adminDto", adminDto);
result.toString();
return "reqister";
}
String username = adminDto.getUsername();
Admin admin = adminService.findByUsername(username);
if(admin != null){
model.addAttribute("adminDto", adminDto);
System.out.println("admin not null");
model.addAttribute("emailError","Your email has been registered!");
return "register";
}
if(adminDto = getPassword().equals(adminDto.getRepeatPassword())) {
adminDto.setPassword(passwordEncoder.encode(adminDto.getPassword()));
adminService.save(adminDto);
System.out.println("success");
model.addAttribute("success", "Register successfully!");
model.addAttribute("adminDto", adminDto);
}else{
model.addAttribute("adminDto", adminDto);
model.addAttribute("passwordError", "your password maybe wrong! Check again!");
System.out.println("password not same");
return "register";
}
} catch (Exception e) {
e.printStackTrace();
model.addAttribute("errors", "The server has been wrong!");
}
return "reqister";
}
②Admin/src/main/resources/templates/register.htmlを編集する
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org/" >
<head th:replace="fragments::header">
</head>
<body class="bg-gradient-primary">
<div class="container">
<div class="card o-hidden border-0 shadow-lg my-5">
<div class="card-body p-0">
<!-- Nested Row within Card Body -->
<div class="row">
<div class="col-lg-5 d-none d-lg-block bg-register-image"></div>
<div class="col-lg-7">
<div class="p-5">
<div class="text-center">
<h1 class="h4 text-gray-900 mb-4">Create an Account!</h1>
</div>
<div th:if="${emailError}">
<p class="alert alert-danger" th:text="${emailError}"></p>
</div>
<div th:if="${errors}">
<p class="alert alert-danger" th:text="${errors}"></p>
</div>
<div th:if="${success}">
<p class="alert alert-success" th:text="${success}"></p>
</div>
<div th:if="${passwordError}">
<p class="alert alert-danger" th:text="${passwordError}"></p>
</div>
<form th:action="@{/register-new}" th:object="${adminDto}" method="POST">
<div class="form-group row">
<div class="col-sm-6 mb-3 mb-sm-0">
<input type="text" class="form-control form-control-user"
th:field = "*{firstName}"
id="exampleFirstName"
placeholder="First Name">
<div class="alert alert-danger" th:if="${#field.hasErrors('firstName')}"
th:errors="*{firstName}"></div>
</div>
<div class="col-sm-6">
<input type="text" th:field = "*{lastName}"
class="form-control form-control-user"
id="exampleLastName"
placeholder="Last Name">
<div class="alert alert-danger" th:if="${#field.hasErrors('lastName')}"
th:errors="*{lastName}"></div>
</div>
</div>
<div class="form-group">
<input type="email" th:field = "*{username}" class="form-control form-control-user" id="exampleInputEmail"
placeholder="Email Username">
</div>
<div class="form-group row">
<div class="col-sm-6 mb-3 mb-sm-0">
<input type="password" th:field = "*{password}" class="form-control form-control-user"
id="exampleInputPassword" placeholder="Password">
<div class="alert alert-danger" th:if="${#field.hasErrors('Password')}"
th:errors="*{Password}"></div>
</div>
</div>
<div class="col-sm-6">
<input type="password" th:field = "*{repeatPassword}" class="form-control form-control-user"
id="exampleRepeatPassword" placeholder="Repeat Password">
</div>
<button type="submit" class="btn btn-primary btn-user btn-block">
Register Account
</button>
<hr>
</form>
<hr>
<div class="text-center">
<a class="small" th:href = "@{/forgot-password}" href="forgot-password.html">Forgot Password?</a>
</div>
<div class="text-center">
<a class="small" th:href = "@{/login}" href="login.html">Already have an account? Login!</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div th:replace="script"></div>
</body>
</html>
③Admin/src/java/com.example.admin/config/AdminConfiguration.javaを作成する
package com.example.admin.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
public class AdminConfiguration extends WebSecurityConfigurerAdapter {
@Bean
public UserDetailsService userDetailsService(){
return new AdminServiceConfig();
}
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider(){
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService());
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
@Override
protected void configure(AuthenticationMagagerBuilder auth) throws Exception{
auth.authenticationProvider(daoAuthenticationProvider());
}
@Override
protected void configure(HttpSecurity htto) throws Exception{
http.authorizeRequests().authMatchers("/*").permitAll()
.antMatcher("/admin/*")
.hasAuthority("ADMIN")
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/do-login")
.defaultSuccessUrl("/index")
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutRequestMacther(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.permitAll();
}
}
④Admin/src/main/resources/templatesにindx.html, 404.html, buttons.html, cards.html, charts.html, forget-password.html table.html, utilities-animation.html, utilities-animation.html, utilities-border.html,
utilities-color.html, utilities-other.html を加える
⑤Admin/src/main/resources/templates/login.htmlを編集する
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org/" xmlns="http://www.w3.org/1999/html">
<head th:replace="fragments :: header">
</head>
<body class="bg-gradient-primary">
<div class="container">
<!-- Outer Row -->
<div class="row justify-content-center">
<div class="col-xl-10 col-lg-12 col-md-9">
<div class="card o-hidden border-0 shadow-lg my-5">
<div class="card-body p-0">
<!-- Nested Row within Card Body -->
<div class="row">
<div class="col-lg-6 d-none d-lg-block bg-login-image"></div>
<div class="col-lg-6">
<div class="p-5">
<div class="text-center">
<h1 class="h4 text-gray-900 mb-4">Welcome Back!</h1>
</div>
<div th:if="${param.error}" class="alert alert-danger text-center">
Invalid username or password
</div>
<div th:if="${param.error}" class="alert alert-success text-center">
You have been logged out!
</div>
<form class="user" th:action="${do-login}" method="post">
<div class="form-group">
<input type="email" name="username" class="form-control form-control-user"
id="username" aria-describedby="emailHelp"
placeholder="Enter Email Address...">
</div>
<div class="form-group">
<input type="password" name="username" class="form-control form-control-user"
id="password" placeholder="Password">
</div>
<button type ="submit" class="btn btn-primary btn-user btn-block">
Login
</button>
<hr>
</form>
<hr>
<div class="text-center">
<a class="small" th:href="@{/forgot-password}">Forgot Password?</a>
</div>
<div class="text-center">
<a class="small" th:href="@{/register}">Create an Account!</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div th:replace="script"></div>
</body>
</html>
⑥Admin/src/main/resources/templatesにHTML5ファイルfragments.htmlを編集する
<!DOCTYPE html>
<html lang="en" xmlns:th = "https://www.thymeleaf.org/">
<head th:fragment="header">
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title th:text = "${title}"></title>
<!-- Custom fonts for this template-->
<link th:href="@{vendor/fontawesome-free/css/all.min.css}" rel="stylesheet" type="text/css">
<link
href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i"
rel="stylesheet">
<!-- Custom styles for this template-->
<link th:href="@{css/sb-admin-2.min.css}" rel="stylesheet">
</head>
<body>
<div th:fragments = "script">
<!-- Bootstrap core JavaScript-->
<script th:src="@{vendor/jquery/jquery.min.js}"></script>
<script th:src="@{vendor/bootstrap/js/bootstrap.bundle.min.js}"></script>
<!-- Core plugin JavaScript-->
<script th:src="{@vendor/jquery-easing/jquery.easing.min.js}"></script>
<!-- Custom scripts for all pages-->
<script th:src="@{js/sb-admin-2.min.js}"></script>
</div>
</body>
</html>
⑦Admin/src/main/resources/templates/index.htmlを編集する
<!DOCTYPE html>
<html lang="en">
<head th :replace = "fragments::header">
</head>
<body id="page-top">
<!-- Page Wrapper -->
<div id="wrapper">
<!-- Sidebar -->
<ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar">
<!-- Sidebar - Brand -->
<a class="sidebar-brand d-flex align-items-center justify-content-center" href="index.html">
<div class="sidebar-brand-icon rotate-n-15">
<i class="fas fa-laugh-wink"></i>
</div>
<div class="sidebar-brand-text mx-3">SB Admin <sup>2</sup></div>
</a>
<!-- Divider -->
<hr class="sidebar-divider my-0">
<!-- Nav Item - Dashboard -->
<li class="nav-item active">
<a class="nav-link" href="index.html">
<i class="fas fa-fw fa-tachometer-alt"></i>
<span>Dashboard</span></a>
</li>
<!-- Divider -->
<hr class="sidebar-divider">
<!-- Heading -->
<div class="sidebar-heading">
Interface
</div>
<!-- Nav Item - Pages Collapse Menu -->
<li class="nav-item">
<a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseTwo"
aria-expanded="true" aria-controls="collapseTwo">
<i class="fas fa-fw fa-cog"></i>
<span>Components</span>
</a>
<div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#accordionSidebar">
<div class="bg-white py-2 collapse-inner rounded">
<h6 class="collapse-header">Custom Components:</h6>
<a class="collapse-item" href="buttons.html">Buttons</a>
<a class="collapse-item" href="cards.html">Cards</a>
</div>
</div>
</li>
<!-- Nav Item - Utilities Collapse Menu -->
<li class="nav-item">
<a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseUtilities"
aria-expanded="true" aria-controls="collapseUtilities">
<i class="fas fa-fw fa-wrench"></i>
<span>Utilities</span>
</a>
<div id="collapseUtilities" class="collapse" aria-labelledby="headingUtilities"
data-parent="#accordionSidebar">
<div class="bg-white py-2 collapse-inner rounded">
<h6 class="collapse-header">Custom Utilities:</h6>
<a class="collapse-item" href="utilities-color.html">Colors</a>
<a class="collapse-item" href="utilities-border.html">Borders</a>
<a class="collapse-item" href="utilities-animation.html">Animations</a>
<a class="collapse-item" href="utilities-other.html">Other</a>
</div>
</div>
</li>
<!-- Divider -->
<hr class="sidebar-divider">
<!-- Heading -->
<div class="sidebar-heading">
Addons
</div>
<!-- Nav Item - Pages Collapse Menu -->
<li class="nav-item">
<a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapsePages"
aria-expanded="true" aria-controls="collapsePages">
<i class="fas fa-fw fa-folder"></i>
<span>Pages</span>
</a>
<div id="collapsePages" class="collapse" aria-labelledby="headingPages" data-parent="#accordionSidebar">
<div class="bg-white py-2 collapse-inner rounded">
<h6 class="collapse-header">Login Screens:</h6>
<a class="collapse-item" href="login.html">Login</a>
<a class="collapse-item" href="register.html">Register</a>
<a class="collapse-item" href="forgot-password.html">Forgot Password</a>
<div class="collapse-divider"></div>
<h6 class="collapse-header">Other Pages:</h6>
<a class="collapse-item" href="404.html">404 Page</a>
<a class="collapse-item" href="blank.html">Blank Page</a>
</div>
</div>
</li>
<!-- Nav Item - Charts -->
<li class="nav-item">
<a class="nav-link" href="charts.html">
<i class="fas fa-fw fa-chart-area"></i>
<span>Charts</span></a>
</li>
<!-- Nav Item - Tables -->
<li class="nav-item">
<a class="nav-link" href="tables.html">
<i class="fas fa-fw fa-table"></i>
<span>Tables</span></a>
</li>
<!-- Divider -->
<hr class="sidebar-divider d-none d-md-block">
<!-- Sidebar Toggler (Sidebar) -->
<div class="text-center d-none d-md-inline">
<button class="rounded-circle border-0" id="sidebarToggle"></button>
</div>
<!-- Sidebar Message -->
<div class="sidebar-card d-none d-lg-flex">
<img class="sidebar-card-illustration mb-2" src="img/undraw_rocket.svg" alt="...">
<p class="text-center mb-2"><strong>SB Admin Pro</strong> is packed with premium features, components, and more!</p>
<a class="btn btn-success btn-sm" href="https://startbootstrap.com/theme/sb-admin-pro">Upgrade to Pro!</a>
</div>
</ul>
<!-- End of Sidebar -->
<!-- Content Wrapper -->
<div id="content-wrapper" class="d-flex flex-column">
<!-- Main Content -->
<div id="content">
<!-- Topbar -->
<nav class="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow">
<!-- Sidebar Toggle (Topbar) -->
<button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3">
<i class="fa fa-bars"></i>
</button>
<!-- Topbar Search -->
<form
class="d-none d-sm-inline-block form-inline mr-auto ml-md-3 my-2 my-md-0 mw-100 navbar-search">
<div class="input-group">
<input type="text" class="form-control bg-light border-0 small" placeholder="Search for..."
aria-label="Search" aria-describedby="basic-addon2">
<div class="input-group-append">
<button class="btn btn-primary" type="button">
<i class="fas fa-search fa-sm"></i>
</button>
</div>
</div>
</form>
<!-- Topbar Navbar -->
<ul class="navbar-nav ml-auto">
<!-- Nav Item - Search Dropdown (Visible Only XS) -->
<li class="nav-item dropdown no-arrow d-sm-none">
<a class="nav-link dropdown-toggle" href="#" id="searchDropdown" role="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-search fa-fw"></i>
</a>
<!-- Dropdown - Messages -->
<div class="dropdown-menu dropdown-menu-right p-3 shadow animated--grow-in"
aria-labelledby="searchDropdown">
<form class="form-inline mr-auto w-100 navbar-search">
<div class="input-group">
<input type="text" class="form-control bg-light border-0 small"
placeholder="Search for..." aria-label="Search"
aria-describedby="basic-addon2">
<div class="input-group-append">
<button class="btn btn-primary" type="button">
<i class="fas fa-search fa-sm"></i>
</button>
</div>
</div>
</form>
</div>
</li>
<!-- Nav Item - Alerts -->
<li class="nav-item dropdown no-arrow mx-1">
<a class="nav-link dropdown-toggle" href="#" id="alertsDropdown" role="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-bell fa-fw"></i>
<!-- Counter - Alerts -->
<span class="badge badge-danger badge-counter">3+</span>
</a>
<!-- Dropdown - Alerts -->
<div class="dropdown-list dropdown-menu dropdown-menu-right shadow animated--grow-in"
aria-labelledby="alertsDropdown">
<h6 class="dropdown-header">
Alerts Center
</h6>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="mr-3">
<div class="icon-circle bg-primary">
<i class="fas fa-file-alt text-white"></i>
</div>
</div>
<div>
<div class="small text-gray-500">December 12, 2019</div>
<span class="font-weight-bold">A new monthly report is ready to download!</span>
</div>
</a>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="mr-3">
<div class="icon-circle bg-success">
<i class="fas fa-donate text-white"></i>
</div>
</div>
<div>
<div class="small text-gray-500">December 7, 2019</div>
$290.29 has been deposited into your account!
</div>
</a>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="mr-3">
<div class="icon-circle bg-warning">
<i class="fas fa-exclamation-triangle text-white"></i>
</div>
</div>
<div>
<div class="small text-gray-500">December 2, 2019</div>
Spending Alert: We've noticed unusually high spending for your account.
</div>
</a>
<a class="dropdown-item text-center small text-gray-500" href="#">Show All Alerts</a>
</div>
</li>
<!-- Nav Item - Messages -->
<li class="nav-item dropdown no-arrow mx-1">
<a class="nav-link dropdown-toggle" href="#" id="messagesDropdown" role="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-envelope fa-fw"></i>
<!-- Counter - Messages -->
<span class="badge badge-danger badge-counter">7</span>
</a>
<!-- Dropdown - Messages -->
<div class="dropdown-list dropdown-menu dropdown-menu-right shadow animated--grow-in"
aria-labelledby="messagesDropdown">
<h6 class="dropdown-header">
Message Center
</h6>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="dropdown-list-image mr-3">
<img class="rounded-circle" src="img/undraw_profile_1.svg"
alt="...">
<div class="status-indicator bg-success"></div>
</div>
<div class="font-weight-bold">
<div class="text-truncate">Hi there! I am wondering if you can help me with a
problem I've been having.</div>
<div class="small text-gray-500">Emily Fowler · 58m</div>
</div>
</a>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="dropdown-list-image mr-3">
<img class="rounded-circle" src="img/undraw_profile_2.svg"
alt="...">
<div class="status-indicator"></div>
</div>
<div>
<div class="text-truncate">I have the photos that you ordered last month, how
would you like them sent to you?</div>
<div class="small text-gray-500">Jae Chun · 1d</div>
</div>
</a>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="dropdown-list-image mr-3">
<img class="rounded-circle" src="img/undraw_profile_3.svg"
alt="...">
<div class="status-indicator bg-warning"></div>
</div>
<div>
<div class="text-truncate">Last month's report looks great, I am very happy with
the progress so far, keep up the good work!</div>
<div class="small text-gray-500">Morgan Alvarez · 2d</div>
</div>
</a>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="dropdown-list-image mr-3">
<img class="rounded-circle" src="https://source.unsplash.com/Mv9hjnEUHR4/60x60"
alt="...">
<div class="status-indicator bg-success"></div>
</div>
<div>
<div class="text-truncate">Am I a good boy? The reason I ask is because someone
told me that people say this to all dogs, even if they aren't good...</div>
<div class="small text-gray-500">Chicken the Dog · 2w</div>
</div>
</a>
<a class="dropdown-item text-center small text-gray-500" href="#">Read More Messages</a>
</div>
</li>
<div class="topbar-divider d-none d-sm-block"></div>
<!-- Nav Item - User Information -->
<li class="nav-item dropdown no-arrow">
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="mr-2 d-none d-lg-inline text-gray-600 small">Douglas McGee</span>
<img class="img-profile rounded-circle"
src="img/undraw_profile.svg">
</a>
<!-- Dropdown - User Information -->
<div class="dropdown-menu dropdown-menu-right shadow animated--grow-in"
aria-labelledby="userDropdown">
<a class="dropdown-item" href="#">
<i class="fas fa-user fa-sm fa-fw mr-2 text-gray-400"></i>
Profile
</a>
<a class="dropdown-item" href="#">
<i class="fas fa-cogs fa-sm fa-fw mr-2 text-gray-400"></i>
Settings
</a>
<a class="dropdown-item" href="#">
<i class="fas fa-list fa-sm fa-fw mr-2 text-gray-400"></i>
Activity Log
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#logoutModal">
<i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i>
Logout
</a>
</div>
</li>
</ul>
</nav>
<!-- End of Topbar -->
<!-- Begin Page Content -->
<div class="container-fluid">
<!-- Page Heading -->
<div class="d-sm-flex align-items-center justify-content-between mb-4">
<h1 class="h3 mb-0 text-gray-800">Dashboard</h1>
<a href="#" class="d-none d-sm-inline-block btn btn-sm btn-primary shadow-sm"><i
class="fas fa-download fa-sm text-white-50"></i> Generate Report</a>
</div>
<!-- Content Row -->
<div class="row">
<!-- Earnings (Monthly) Card Example -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-primary shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">
Earnings (Monthly)</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">$40,000</div>
</div>
<div class="col-auto">
<i class="fas fa-calendar fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Earnings (Monthly) Card Example -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-success shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">
Earnings (Annual)</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">$215,000</div>
</div>
<div class="col-auto">
<i class="fas fa-dollar-sign fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Earnings (Monthly) Card Example -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-info shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">Tasks
</div>
<div class="row no-gutters align-items-center">
<div class="col-auto">
<div class="h5 mb-0 mr-3 font-weight-bold text-gray-800">50%</div>
</div>
<div class="col">
<div class="progress progress-sm mr-2">
<div class="progress-bar bg-info" role="progressbar"
style="width: 50%" aria-valuenow="50" aria-valuemin="0"
aria-valuemax="100"></div>
</div>
</div>
</div>
</div>
<div class="col-auto">
<i class="fas fa-clipboard-list fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Pending Requests Card Example -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-warning shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">
Pending Requests</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">18</div>
</div>
<div class="col-auto">
<i class="fas fa-comments fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Content Row -->
<div class="row">
<!-- Area Chart -->
<div class="col-xl-8 col-lg-7">
<div class="card shadow mb-4">
<!-- Card Header - Dropdown -->
<div
class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
<h6 class="m-0 font-weight-bold text-primary">Earnings Overview</h6>
<div class="dropdown no-arrow">
<a class="dropdown-toggle" href="#" role="button" id="dropdownMenuLink"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-ellipsis-v fa-sm fa-fw text-gray-400"></i>
</a>
<div class="dropdown-menu dropdown-menu-right shadow animated--fade-in"
aria-labelledby="dropdownMenuLink">
<div class="dropdown-header">Dropdown Header:</div>
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</div>
</div>
<!-- Card Body -->
<div class="card-body">
<div class="chart-area">
<canvas id="myAreaChart"></canvas>
</div>
</div>
</div>
</div>
<!-- Pie Chart -->
<div class="col-xl-4 col-lg-5">
<div class="card shadow mb-4">
<!-- Card Header - Dropdown -->
<div
class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
<h6 class="m-0 font-weight-bold text-primary">Revenue Sources</h6>
<div class="dropdown no-arrow">
<a class="dropdown-toggle" href="#" role="button" id="dropdownMenuLink"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-ellipsis-v fa-sm fa-fw text-gray-400"></i>
</a>
<div class="dropdown-menu dropdown-menu-right shadow animated--fade-in"
aria-labelledby="dropdownMenuLink">
<div class="dropdown-header">Dropdown Header:</div>
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</div>
</div>
<!-- Card Body -->
<div class="card-body">
<div class="chart-pie pt-4 pb-2">
<canvas id="myPieChart"></canvas>
</div>
<div class="mt-4 text-center small">
<span class="mr-2">
<i class="fas fa-circle text-primary"></i> Direct
</span>
<span class="mr-2">
<i class="fas fa-circle text-success"></i> Social
</span>
<span class="mr-2">
<i class="fas fa-circle text-info"></i> Referral
</span>
</div>
</div>
</div>
</div>
</div>
<!-- Content Row -->
<div class="row">
<!-- Content Column -->
<div class="col-lg-6 mb-4">
<!-- Project Card Example -->
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Projects</h6>
</div>
<div class="card-body">
<h4 class="small font-weight-bold">Server Migration <span
class="float-right">20%</span></h4>
<div class="progress mb-4">
<div class="progress-bar bg-danger" role="progressbar" style="width: 20%"
aria-valuenow="20" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<h4 class="small font-weight-bold">Sales Tracking <span
class="float-right">40%</span></h4>
<div class="progress mb-4">
<div class="progress-bar bg-warning" role="progressbar" style="width: 40%"
aria-valuenow="40" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<h4 class="small font-weight-bold">Customer Database <span
class="float-right">60%</span></h4>
<div class="progress mb-4">
<div class="progress-bar" role="progressbar" style="width: 60%"
aria-valuenow="60" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<h4 class="small font-weight-bold">Payout Details <span
class="float-right">80%</span></h4>
<div class="progress mb-4">
<div class="progress-bar bg-info" role="progressbar" style="width: 80%"
aria-valuenow="80" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<h4 class="small font-weight-bold">Account Setup <span
class="float-right">Complete!</span></h4>
<div class="progress">
<div class="progress-bar bg-success" role="progressbar" style="width: 100%"
aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</div>
<!-- Color System -->
<div class="row">
<div class="col-lg-6 mb-4">
<div class="card bg-primary text-white shadow">
<div class="card-body">
Primary
<div class="text-white-50 small">#4e73df</div>
</div>
</div>
</div>
<div class="col-lg-6 mb-4">
<div class="card bg-success text-white shadow">
<div class="card-body">
Success
<div class="text-white-50 small">#1cc88a</div>
</div>
</div>
</div>
<div class="col-lg-6 mb-4">
<div class="card bg-info text-white shadow">
<div class="card-body">
Info
<div class="text-white-50 small">#36b9cc</div>
</div>
</div>
</div>
<div class="col-lg-6 mb-4">
<div class="card bg-warning text-white shadow">
<div class="card-body">
Warning
<div class="text-white-50 small">#f6c23e</div>
</div>
</div>
</div>
<div class="col-lg-6 mb-4">
<div class="card bg-danger text-white shadow">
<div class="card-body">
Danger
<div class="text-white-50 small">#e74a3b</div>
</div>
</div>
</div>
<div class="col-lg-6 mb-4">
<div class="card bg-secondary text-white shadow">
<div class="card-body">
Secondary
<div class="text-white-50 small">#858796</div>
</div>
</div>
</div>
<div class="col-lg-6 mb-4">
<div class="card bg-light text-black shadow">
<div class="card-body">
Light
<div class="text-black-50 small">#f8f9fc</div>
</div>
</div>
</div>
<div class="col-lg-6 mb-4">
<div class="card bg-dark text-white shadow">
<div class="card-body">
Dark
<div class="text-white-50 small">#5a5c69</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-6 mb-4">
<!-- Illustrations -->
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Illustrations</h6>
</div>
<div class="card-body">
<div class="text-center">
<img class="img-fluid px-3 px-sm-4 mt-3 mb-4" style="width: 25rem;"
src="img/undraw_posting_photo.svg" alt="...">
</div>
<p>Add some quality, svg illustrations to your project courtesy of <a
target="_blank" rel="nofollow" href="https://undraw.co/">unDraw</a>, a
constantly updated collection of beautiful svg images that you can use
completely free and without attribution!</p>
<a target="_blank" rel="nofollow" href="https://undraw.co/">Browse Illustrations on
unDraw →</a>
</div>
</div>
<!-- Approach -->
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Development Approach</h6>
</div>
<div class="card-body">
<p>SB Admin 2 makes extensive use of Bootstrap 4 utility classes in order to reduce
CSS bloat and poor page performance. Custom CSS classes are used to create
custom components and custom utility classes.</p>
<p class="mb-0">Before working with this theme, you should become familiar with the
Bootstrap framework, especially the utility classes.</p>
</div>
</div>
</div>
</div>
</div>
<!-- /.container-fluid -->
</div>
<!-- End of Main Content -->
<!-- Footer -->
<footer class="sticky-footer bg-white">
<div class="container my-auto">
<div class="copyright text-center my-auto">
<span>Copyright © Your Website 2021</span>
</div>
</div>
</footer>
<!-- End of Footer -->
</div>
<!-- End of Content Wrapper -->
</div>
<!-- End of Page Wrapper -->
<!-- Scroll to Top Button-->
<a class="scroll-to-top rounded" href="#page-top">
<i class="fas fa-angle-up"></i>
</a>
<!-- Logout Modal-->
<div class="modal fade" id="logoutModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Ready to Leave?</h5>
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">Select "Logout" below if you are ready to end your current session.</div>
<div class="modal-footer">
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
<a class="btn btn-primary" href="login.html">Logout</a>
</div>
</div>
</div>
</div>
<div th:replace="fragments::script"></div>
<!-- Page level plugins -->
<script th:src="@{/vendor/chart.js/Chart.min.js}"></script>
<!-- Page level custom scripts -->
<script th:src="@{/js/demo/chart-area-demo.js}"></script>
<script src="@{/js/demo/chart-pie-demo.js}"></script>
</body>
5.CRUD と カテゴリ付きの Thymeleaf
①Admin/src/main/resources/templates/register.htmlを編集する
②Admin/src/main/resources/templates/login.htmlを編集する
③Admin/src/main/resources/templates/forgot-password.htmlを編集する
④Library/src/main/java/com.ecommerce.library/modelにCategory.javaを作成する
⑤Library/src/main/java/com.ecommerce.library/modelにProduct.javaを作成する
⑥Library/src/main/java/com.ecommerce.library/repositoryに
CategoryRepository.javaを作成する
⑦Library/src/main/java/com.ecommerce.library/repositoryに
ProductRepository.javaを作成する
⑧Library/src/main/java/com.ecommerce.library/serviceにCategoryService.javaを作成する
⑨Library/src/main/java/com.ecommerce.library/service/implにCategoryService Impl.javaを作成する
参考サイト
How to setup and use Git and GitHub with IntelliJ IDEA [2023] | Git | GitHub | IntelliJ
Create Multi-module Project in IntelliJ IDEA |Part 1 | Self-Code |Ecommerce Web Spring Boot Project
Configure Spring Security for Admin Module | Part 2 | Self-Code | Ecommerce Web Spring Boot Project
Register Function(Done), Fragment Thymeleaf | Part 4 | Self-Code | Ecommerce Web Spring Boot Project
CRUD in Spring Boot - Thymeleaf with Category |Part 5|Self-Code || Ecommerce Web Spring Boot Project