開発環境
- Apache NetBeans IDE 17
- MySQL 8.0.31
- JDK 1.8
- Apache Tomcat 10.0.12
プロジェクト構造
実装コード
JSPファイル
signin.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<html>
<head>
<title>ログイン</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
margin: 0;
padding: 20px;
}
h1 {
text-align: center;
color: #333;
}
.login-form {
max-width: 500px;
margin: 30px auto;
padding: 25px;
border-radius: 8px;
background-color: white;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: bold;
}
.form-control {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
font-size: 16px;
}
.btn {
background-color: #4285f4;
color: white;
border: none;
padding: 12px 20px;
border-radius: 4px;
cursor: pointer;
width: 100%;
font-size: 16px;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #3367d6;
}
.text-right {
text-align: right;
margin-top: 15px;
}
.text-right a {
color: #4285f4;
text-decoration: none;
}
.text-right a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<h1>ログインページ</h1>
<form method="post" action="AuthenticationServlet" class="login-form">
<div class="form-group">
<label for="userId">ユーザー名:</label>
<input type="text" placeholder="ユーザー名を入力" name="userId" class="form-control" required>
</div>
<div class="form-group">
<label for="userPass">パスワード:</label>
<input type="password" placeholder="パスワードを入力" name="userPass" class="form-control" required>
</div>
<button type="submit" class="btn">ログイン</button>
<div class="text-right">
<a href="signup.jsp">アカウントをお持ちでない方はこちら</a>
</div>
</form>
</body>
</html>
signup.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<html>
<head>
<title>新規登録</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
margin: 0;
padding: 20px;
}
h1 {
text-align: center;
color: #333;
}
.register-form {
max-width: 500px;
margin: 30px auto;
padding: 25px;
border-radius: 8px;
background-color: white;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: bold;
}
.form-control {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
font-size: 16px;
}
.btn {
background-color: #34a853;
color: white;
border: none;
padding: 12px 20px;
border-radius: 4px;
cursor: pointer;
width: 100%;
font-size: 16px;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #2d8e47;
}
.text-right {
text-align: right;
margin-top: 15px;
}
.text-right a {
color: #4285f4;
text-decoration: none;
}
.text-right a:hover {
text-decoration: underline;
}
.error {
color: #ea4335;
font-size: 14px;
margin-top: 5px;
display: none;
}
.success {
color: #34a853;
font-size: 14px;
margin-top: 5px;
display: none;
}
.password-requirements {
font-size: 14px;
color: #5f6368;
margin-top: 5px;
}
</style>
</head>
<body>
<h1>新規アカウント作成</h1>
<form method="post" action="RegistrationServlet" class="register-form">
<div class="form-group">
<label for="newUserId">ユーザー名:</label>
<input type="text" placeholder="ユーザー名を入力" name="newUserId" class="form-control" required>
</div>
<div class="form-group">
<label for="newUserPass">パスワード:</label>
<input type="password" placeholder="パスワードを入力" name="newUserPass" id="newUserPass"
pattern="^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[0-9a-zA-Z]{8,}$" class="form-control" required>
<p class="password-requirements">パスワードは大文字、小文字、数字を含み、8文字以上である必要があります</p>
<p class="error" id="passwordError">パスワードが要件を満たしていません</p>
</div>
<div class="form-group">
<label for="confirmPass">パスワード(確認):</label>
<input type="password" placeholder="パスワードを再入力" name="confirmPass" id="confirmPass" class="form-control" required>
<p class="error" id="matchError">パスワードが一致しません</p>
<p class="success" id="matchSuccess">パスワードが一致しました</p>
</div>
<button type="submit" class="btn">アカウント作成</button>
<div class="text-right">
<a href="signin.jsp">既にアカウントをお持ちの方はこちら</a>
</div>
</form>
<script>
// パスワードの検証
const passwordField = document.getElementById("newUserPass");
const confirmField = document.getElementById("confirmPass");
const passwordError = document.getElementById("passwordError");
const matchError = document.getElementById("matchError");
const matchSuccess = document.getElementById("matchSuccess");
function validatePasswordStrength() {
const regex = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[0-9a-zA-Z]{8,}$/;
if (regex.test(passwordField.value)) {
passwordError.style.display = "none";
return true;
} else {
passwordError.style.display = "block";
return false;
}
}
function validatePasswordMatch() {
if (passwordField.value === confirmField.value && passwordField.value !== "") {
matchError.style.display = "none";
matchSuccess.style.display = "block";
return true;
} else {
matchError.style.display = "block";
matchSuccess.style.display = "none";
return false;
}
}
passwordField.addEventListener("input", function() {
validatePasswordStrength();
if (confirmField.value !== "") {
validatePasswordMatch();
}
});
confirmField.addEventListener("input", validatePasswordMatch);
</script>
</body>
</html>
dashboard.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<html>
<head>
<meta charset="utf-8">
<title>ペットクリニック</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #333;
}
/* ナビゲーションバー */
.navigation {
background-color: #2c3e50;
padding: 15px 0;
}
.nav-container {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
}
.logo {
color: white;
font-size: 24px;
font-weight: bold;
text-decoration: none;
}
.nav-links {
display: flex;
}
.nav-links a {
color: white;
text-decoration: none;
padding: 10px 15px;
margin-left: 5px;
border-radius: 4px;
transition: background-color 0.3s;
}
.nav-links a:hover {
background-color: #34495e;
}
/* ヒーローセクション */
.hero {
position: relative;
height: 500px;
overflow: hidden;
}
.hero-slide {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
transition: opacity 1s ease-in-out;
}
.hero-slide.active {
opacity: 1;
}
.hero-slide img {
width: 100%;
height: 100%;
object-fit: cover;
}
.slide-indicators {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
}
.indicator {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.5);
cursor: pointer;
}
.indicator.active {
background-color: white;
}
/* サービスセクション */
.services {
padding: 80px 20px;
text-align: center;
background-color: #f9f9f9;
}
.section-title {
font-size: 36px;
margin-bottom: 50px;
color: #2c3e50;
}
.services-container {
max-width: 1200px;
margin: 0 auto;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 30px;
}
.service-card {
flex: 1;
min-width: 300px;
max-width: 350px;
background: white;
border-radius: 8px;
padding: 30px;
box-shadow: 0 5px 15px rgba(0,0,0,0.05);
transition: transform 0.3s, box-shadow 0.3s;
}
.service-card:hover {
transform: translateY(-10px);
box-shadow: 0 15px 30px rgba(0,0,0,0.1);
}
.service-icon {
font-size: 40px;
margin-bottom: 20px;
color: #3498db;
}
.service-title {
font-size: 24px;
margin-bottom: 15px;
color: #2c3e50;
}
.service-description {
color: #7f8c8d;
line-height: 1.8;
}
/* ニュースセクション */
.news {
padding: 80px 20px;
background-color: #2c3e50;
color: white;
}
.news-container {
max-width: 1200px;
margin: 0 auto;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 30px;
}
.news-card {
flex: 1;
min-width: 300px;
max-width: 350px;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.news-content {
padding: 25px;
color: #333;
}
.news-title {
font-size: 20px;
margin-bottom: 15px;
color: #2c3e50;
}
.news-excerpt {
color: #7f8c8d;
line-height: 1.6;
}
/* 連絡先セクション */
.contact {
padding: 80px 20px;
text-align: center;
background-color: #f9f9f9;
}
.contact-info {
max-width: 800px;
margin: 0 auto;
}
.contact-item {
margin-bottom: 20px;
font-size: 18px;
}
.contact-item strong {
color: #2c3e50;
display: inline-block;
width: 100px;
}
/* フッター */
footer {
background-color: #2c3e50;
color: white;
text-align: center;
padding: 20px;
}
</style>
</head>
<body>
<!-- ナビゲーションバー -->
<nav class="navigation">
<div class="nav-container">
<a href="#" class="logo">ペットクリニック</a>
<div class="nav-links">
<a href="#">ホーム</a>
<a href="#">サービス</a>
<a href="#">スタッフ</a>
<a href="#">お知らせ</a>
<a href="#">お問い合わせ</a>
</div>
</div>
</nav>
<!-- ヒーローセクション -->
<div class="hero">
<div class="hero-slide active">
<img src="https://picsum.photos/1200/500/?random=1" alt="ペットケア">
</div>
<div class="hero-slide">
<img src="https://picsum.photos/1200/500/?random=2" alt="ペット診療">
</div>
<div class="hero-slide">
<img src="https://picsum.photos/1200/500/?random=3" alt="ペット健康">
</div>
<div class="slide-indicators">
<span class="indicator active" onclick="currentSlide(1)"></span>
<span class="indicator" onclick="currentSlide(2)"></span>
<span class="indicator" onclick="currentSlide(3)"></span>
</div>
</div>
<!-- サービスセクション -->
<section class="services">
<h2 class="section-title">当院のサービス</h2>
<div class="services-container">
<div class="service-card">
<div class="service-icon">💉</div>
<h3 class="service-title">ワクチン接種</h3>
<p class="service-description">ペットのための各種ワクチン接種と予防医療サービスを提供し、健康な成長をサポートします。</p>
</div>
<div class="service-card">
<div class="service-icon">🏥</div>
<h3 class="service-title">外科手術</h3>
<p class="service-description">経験豊富な外科医チームによる、ペットのための各種手術を専門に行っています。</p>
</div>
<div class="service-card">
<div class="service-icon">🔬</div>
<h3 class="service-title">精密検査</h3>
<p class="service-description">最新の診断機器を活用し、迅速かつ正確な診断で最適な治療計画を立てます。</p>
</div>
</div>
</section>
<!-- ニュースセクション -->
<section class="news">
<h2 class="section-title">最新情報</h2>
<div class="news-container">
<div class="news-card">
<div class="news-content">
<h3 class="news-title">ペットの正しいシャンプーの方法</h3>
<p class="news-excerpt">ペットの日常ケアにおいてシャンプーは重要ですが、正しい方法で行わないと健康に悪影響を及ぼす可能性があります。</p>
</div>
</div>
<div class="news-card">
<div class="news-content">
<h3 class="news-title">ペットの風邪予防法</h3>
<p class="news-excerpt">寒い季節にはペットも風邪を引きやすくなります。風邪を引いたペットの状態は深刻化することがあるため、予防が重要です。</p>
</div>
</div>
<div class="news-card">
<div class="news-content">
<h3 class="news-title">猫用フードの選び方</h3>
<p class="news-excerpt">猫用フードの品質はペットの健康に直接影響するため、適切なフードを選ぶことが非常に重要です。</p>
</div>
</div>
</div>
</section>
<!-- 連絡先セクション -->
<section class="contact">
<h2 class="section-title">お問い合わせ</h2>
<div class="contact-info">
<div class="contact-item">
<strong>住所</strong>: 〇〇県〇〇市〇〇区〇〇町〇〇番地
</div>
<div class="contact-item">
<strong>電話番号</strong>: 0123-456-7890
</div>
<div class="contact-item">
<strong>メール</strong>: info@pet-clinic.jp
</div>
</div>
</section>
<!-- フッター -->
<footer>
<p>© 2023 ペットクリニック. All rights reserved.</p>
</footer>
<script>
// スライドショーの制御
let slideIndex = 1;
function currentSlide(n) {
showSlide(slideIndex = n);
}
function showSlide(n) {
const slides = document.getElementsByClassName("hero-slide");
const indicators = document.getElementsByClassName("indicator");
if (n > slides.length) {slideIndex = 1}
if (n < 1) {slideIndex = slides.length}
for (let i = 0; i < slides.length; i++) {
slides[i].classList.remove("active");
}
for (let i = 0; i < indicators.length; i++) {
indicators[i].classList.remove("active");
}
slides[slideIndex-1].classList.add("active");
indicators[slideIndex-1].classList.add("active");
}
// 自動スライド
setInterval(function() {
slideIndex++;
if (slideIndex > 3) {
slideIndex = 1;
}
showSlide(slideIndex);
}, 5000);
</script>
</body>
</html>
web.xmlの設定
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
<servlet>
<servlet-name>AuthenticationServlet</servlet-name>
<servlet-class>com.clinic.AuthServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>RegistrationServlet</servlet-name>
<servlet-class>com.clinic.RegServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AuthenticationServlet</servlet-name>
<url-pattern>/AuthenticationServlet</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>RegistrationServlet</servlet-name>
<url-pattern>/RegistrationServlet</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
</web-app>
Javaサーブレットコード
AuthServlet.java
package com.clinic;
import java.io.*;
import java.sql.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class AuthServlet extends HttpServlet {
@Override
public void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
String userIdentifier = req.getParameter("userId");
String userCredentials = req.getParameter("userPass");
// ユーザー認証の実行
boolean authResult = verifyCredentials(userIdentifier, userCredentials);
if (authResult) {
// 認証成功、セッションにユーザー情報を保存
HttpSession userSession = req.getSession(true);
userSession.setAttribute("currentUser", userIdentifier);
// ダッシュボードページへリダイレクト
res.sendRedirect("dashboard.jsp");
} else {
// 認証失敗、ログインページへ戻る
res.sendRedirect("signin.jsp");
}
}
// ユーザー認証ロジック
private boolean verifyCredentials(String loginId, String loginPass) {
boolean isValidUser = false;
try {
// データベースドライバのロード
Class.forName("com.mysql.jdbc.Driver");
// データベース接続の確立
String dbUrl = "jdbc:mysql://localhost:3306/pet_clinic_db";
String dbUser = "admin";
String dbPassword = "password123";
Connection dbConnection = DriverManager.getConnection(dbUrl, dbUser, dbPassword);
// SQLクエリの準備
String query = "SELECT * FROM users WHERE user_id = ? AND user_pass = ?";
PreparedStatement prepStatement = dbConnection.prepareStatement(query);
prepStatement.setString(1, loginId);
prepStatement.setString(2, loginPass);
// クエリの実行
ResultSet queryResult = prepStatement.executeQuery();
// 結果の確認
if (queryResult.next()) {
isValidUser = true;
}
// リソースの解放
queryResult.close();
prepStatement.close();
dbConnection.close();
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return isValidUser;
}
}
RegServlet.java
package com.clinic;
import java.io.*;
import java.sql.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class RegServlet extends HttpServlet {
public void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
String newUserId = req.getParameter("newUserId");
String newUserPass = req.getParameter("newUserPass");
// ユーザー登録ロジックの実行
boolean registrationResult = createUserAccount(newUserId, newUserPass);
if (registrationResult) {
// 登録成功、セッションにユーザー情報を保存
HttpSession userSession = req.getSession(true);
userSession.setAttribute("currentUser", newUserId);
// ダッシュボードページへリダイレクト
res.sendRedirect("dashboard.jsp");
} else {
// 登録失敗、登録ページへ戻る
res.sendRedirect("signup.jsp");
}
}
// ユーザー登録ロジック
private boolean createUserAccount(String userId, String userPass) {
boolean isCreated = false;
try {
// データベースドライバのロード
Class.forName("com.mysql.jdbc.Driver");
// データベース接続の確立
String dbUrl = "jdbc:mysql://localhost:3306/pet_clinic_db";
String dbUser = "admin";
String dbPassword = "password123";
try (Connection dbConnection = DriverManager.getConnection(dbUrl, dbUser, dbPassword)) {
// ユーザーが既に存在するか確認
String checkQuery = "SELECT user_id FROM users WHERE user_id = ?";
try (PreparedStatement checkStmt = dbConnection.prepareStatement(checkQuery)) {
checkStmt.setString(1, userId);
ResultSet checkResult = checkStmt.executeQuery();
if (checkResult.next()) {
// ユーザーが既に存在する場合
checkResult.close();
return false;
}
checkResult.close();
}
// 新規ユーザーの登録
String insertQuery = "INSERT INTO users (user_id, user_pass) VALUES (?, ?)";
try (PreparedStatement insertStmt = dbConnection.prepareStatement(insertQuery)) {
insertStmt.setString(1, userId);
insertStmt.setString(2, userPass);
int rowsAffected = insertStmt.executeUpdate();
if (rowsAffected > 0) {
isCreated = true;
}
}
}
} catch (ClassNotFoundException | SQLException e) {
System.out.println("データベースエラー: " + e.getMessage());
e.printStackTrace();
}
return isCreated;
}
}
データベース設定
MySQLデータベースの作成
-- 1. データベースの作成
CREATE DATABASE pet_clinic_db;
-- 2. データベースの選択
USE pet_clinic_db;
-- 3. usersテーブルの作成
CREATE TABLE users (
user_id VARCHAR(50) NOT NULL,
user_pass VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id)
);
実装された機能
- ユーザーログイン(データベースとの認証連携)
- 新規ユーザー登録(パスワード強度要件、パスワード確認機能付き)
- ペットクリニックのダッシュボードページ