新卒1年目がSpringBoot+Thymeleafでお問い合わせフォームを自作してみた

こんにちは。
興味を持ってくださりありがとうございます。

現在自分で改修している飲食店検索アプリでお問い合わせフォームを
作成したので、備忘録的に記録していきます。

この記事を読むだけでお問い合わせフォームから、
書式に沿ったメールを送信する機能を作れるように書くのが目標です。

# なぜこの記事を書くのか
まずは世の中にあふれているはずのお問い合わせフォーム構築記事を
初学者の自分が書こうかと思ったのかを書いていきます。

## ひとまとまりになっている記事がない
たくさんの記事をみて、あーでもないこーでもないと試行錯誤する時間は
しんどいですが楽しい部分がとても多いです。

ただたくさんの記事に分かれていることの難点は、
次何をすればいいかわかりにくい(調べにくい)だとか、
初学者にとって、本質的に理解して今見ている記事と他の記事との乖離を
埋めるという高難度の対応を求められるだとか色々ありますね。

長期的に進んでいる実感がないと、楽しいものは苦しいものに変わってしまいます。

私は特に二つ目の理由にとても苦しめられたので、
同じ被害者が少しでも少なくなるようにこの記事にまとめていきます。

※実装はできましたが、セキュリティ面はダメダメなのでそのあたりに関しては
追々更新ということでお願いいたします(;^ω^)

# 前提環境
・Java17
・SpringBoot2.7.1
・Thymeleaf2.7.1
・Mavenプロジェクト


# お問い合わせフォームの概要
1.入力された名前、メールアドレス、お問い合わせ種類、お問い合わせ文章をセット
2.アプリ運営側とユーザー側それぞれにお問い合わせ内容確認メールを送信

大まかな仕組みはこのようになっています。


# 前準備
具体的な仕組みに入っていきたいのですが、前準備があるので先にそちらから。
springbootとthymeleafでメールを送信するには、MailSenderやJavaMailSenderを使うことが多いかなと思います。

今回はHTMLメールを作成して送信したいので、JavaMailSenderを使用することにしました。

上記を使用するには、
pom.xmlファイルで依存関係を記述したり、
application.propertiesにメールサーバーへの通信設定をしたり、
送信されたお問い合わせメールを受け取るアカウントの作成、設定をしたりと、前準備がそこそこあります。

というわけで早速コードを貼りますね。
```pom.xml
<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
```

今回はgmailのサーバーを使います。
Outlookを使う場合はspring.mail.hostをsmtp.office365.comに設定する必要があります。

```application.properties
spring.mail.host=smtp.gmail.com
spring.mail.port=587 // tlsでの暗号化通信のための設定
spring.mail.username=お問い合わせを受けるメールアドレス
spring.mail.password=アプリパスワード(後述)

spring.mail.properties.mail.debug=true　// debug出力
spring.mail.properties.mail.transport=smtp　// smtpのおまじない
spring.mail.properties.mail.smtp.auth=true　// サーバーログインさせてください！
spring.mail.properties.mail.smtp.starttls.enable=true　// tls使います！
```

## Googleアカウントの作成とアプリパスワードの設定
Googleアカウントの作成に関しては、
[こちら](https://support.google.com/accounts/answer/27441?hl=ja)から案内に沿って完了できます。

さて、作成が完了したところで次にアプリパスワードの生成に移りますね。
アプリパスワードというのは、二段階認証プロセスを有効にすると生成できる16桁のパスワードで、
今回の用途でいうとgmailのサーバーにログインするために使用するものです。

ちなみに二段階認証を有効にしているか確認するには、Googleアカウントの管理>セキュリティに移動する必要があります。
![アプリパスワード1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2627866/83d85220-91d8-8969-e20f-9bc7cb6e68ae.png)

二段階認証を有効にすることで初めてアプリパスワードの生成が出来ます。
![アプリパスワード2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2627866/3731c79d-c892-aa77-d819-b170cb047d85.png)
1.「アプリを選択」、「デバイスを選択」の二つを設定
※このとき、「その他(名前を入力)」を選択して任意の文字列(判別しやすいものが良い)を入力すると、わざわざ二つ設定しなくてもよいのでおススメです。(私もそうしています)

2.生成されたパスワードをspring.mail.passwordに記載
※生成されたパスワードの画面を消してしまうとそのパスワードを二度と出てこないので、もし消してしまった場合は再作成してくださいね。


これで前準備がすべて完了しました！ヨシ！👈

私はこの前準備で一番手間取りました。
どう調べていいか分からなかったこと、情報があふれかえっていること、初学者にとってpom.xmlやapplication.propertiesはほとんど触ったことがないから記述は簡単でも何をしているのかよくわからなかったこと、以上3点から非常に苦戦しました。

あなたももし同じような心境であれば、次からはいよいよ実装に入っていくわけなので、ものづくり精神が奮い立ちますね。
それでは実装へ！

# 実装部分
メールテンプレート作成の処理に関しては[こちら](https://fumidzuki.com/knowledge/1527/)の記事をまるぱk...参考にさせていただきました。
```ContactController.java
@Controller
public class ContactController {

	// HTMLメールを送信するためにこちらを使用
	@Autowired
	JavaMailSender mailSender;
/**
	 * お問い合わせメール作成から送信
	 * 
	 * @param name お客様の名前
	 * @param email お客様のメールアドレス
	 * @param contact お問い合わせ内容(HTML側でプルダウンメニューによる選択)
	 * @param value お問い合わせ内容文章
	 * 
	 * @return メール送信完了メッセージ表示
	 */
	@PostMapping("/contact")
	public String sendContact(@RequestParam(value = "name", defaultValue = "ARI社員さん") String name,
			@RequestParam("email") String email, @RequestParam("contact") String contact,
			@RequestParam("value") String value) {
		try {
			// contact選択肢のvalueが件名に表示されてしまうのでここで変換
			String subject = convertValueIntoCharacter(contact);
			// メール作成処理
			MimeMessage mailText = mailSender.createMimeMessage();
			MimeMessageHelper msg = new MimeMessageHelper(mailText, true, "UTF-8");
			msg.setFrom(email); // 一応セットしなくてもメール送信できる
			msg.setTo("{お問い合わせを受けるメールアドレス}");
			msg.setSubject(subject);
			msg.setText(makeMailByTemplate(email, name, subject, value), true); // trueにすることでHTMLメールを作成可
			// お問い合わせ側にお問い合わせメール送信完了メールを作成
			contactMailSent(email, name, value);
			// メール送信処理
			this.mailSender.send(mailText);
		} catch (MessagingException e) {
			e.printStackTrace();
		}
		return "sentContact"; // 遷移先HTML

	}

	/**
	 * @param name
	 * @param contact
	 * @param value
	 * @return mailTemplete.htmlに入力内容を埋め込んだテキスト
	 */
	private String makeMailByTemplate(String email, String name, String subject, String value) {
		// テンプレートエンジンを使用するための設定インスタンスを生成します。
		ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
		/*
		 * テンプレートエンジンの種類を指定します。 メールテンプレートとして使用するため、テキストを指定しています。
		 */
		templateResolver.setTemplateMode(TemplateMode.TEXT);
		/*
		 * テンプレートファイルとして読み込む文字エンコードを指定します。 以下のように指定すると「UTF-8」の文字エンコードなります。
		 */
		templateResolver.setCharacterEncoding("UTF-8");

		// テンプレートエンジンを使用するためのインスタンスを生成します。
		SpringTemplateEngine engine = new SpringTemplateEngine();
		engine.setTemplateResolver(templateResolver);

		// メールテンプレートに設定するパラメータを設定します。
		Map<String, Object> texts = new HashMap<>();
		texts.put("email", email);
		texts.put("name", name);
		texts.put("contact", subject);
		texts.put("value", value);

		// テンプレートエンジンを実行してテキストを取得します。
		Context context = new Context();
		context.setVariables(texts);

		// 使用するテンプレートのファイル名とパラメータ情報を設定します。
		return engine.process("/templates/mailTemplate.html", context);

	}

	/**
	 * 件名をvalueから見やすい文字に変換
	 * 
	 * @param contact
	 * @return 変換後のcontact
	 */
	private String convertValueIntoCharacter(String contact) {
		switch (contact) {
		case "opinion":
			contact = "ご意見・ご要望";
			break;
		case "addStore":
			contact = "店舗追加依頼";
			break;
		case "other":
			contact = "その他";
			break;
		default:
			contact = "お問い合わせ";
			break;
		}
		return contact;
	}

	private void contactMailSent(String email, String name, String value) {
		try {
			MimeMessage mailText = mailSender.createMimeMessage();
			MimeMessageHelper msg = new MimeMessageHelper(mailText, true, "UTF-8");
			msg.setFrom("{お問い合わせを受けるメールアドレス}");
			msg.setTo(email);
			msg.setSubject("お問い合わせありがとうございます！");
			msg.setText(makeMailByTemplateToUser(name, value), true); // trueにすることでHTMLメールを作成可
			this.mailSender.send(mailText);
		} catch (MessagingException e) {
			e.printStackTrace();
		}

	}

	private String makeMailByTemplateToUser(String name, String value) {
		// テンプレートエンジンを使用するための設定インスタンスを生成します。
		ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
		/*
		 * テンプレートエンジンの種類を指定します。 メールテンプレートとして使用するため、テキストを指定しています。
		 */
		templateResolver.setTemplateMode(TemplateMode.TEXT);
		/*
		 * テンプレートファイルとして読み込む文字エンコードを指定します。 以下のように指定すると「UTF-8」の文字エンコードなります。
		 */
		templateResolver.setCharacterEncoding("UTF-8");

		// テンプレートエンジンを使用するためのインスタンスを生成します。
		SpringTemplateEngine engine = new SpringTemplateEngine();
		engine.setTemplateResolver(templateResolver);

		// メールテンプレートに設定するパラメータを設定します。
		Map<String, Object> texts = new HashMap<>();
		texts.put("name", name);
		texts.put("value", value);

		// テンプレートエンジンを実行してテキストを取得します。
		Context context = new Context();
		context.setVariables(texts);

		// 使用するテンプレートのファイル名とパラメータ情報を設定します。
		return engine.process("/templates/toUserMailTemplate.html", context);
	}
```
※toUserMailTemplate.htmlは以下のmailTemplate.htmlとほぼ同様のため省略

```mailTemplate.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="ja">
<head>
<meta charset="UTF-8">
<title>contactMailTemplate</title>
</head>
<body>
	<br>
	<span th:text=${name}></span>(<span th:text=${email}></span>)さんから
	<span th:text=${contact}></span>が届いてますよ♪
	<br>
	<strong>==================</strong>
	<br>
	<br>
	<span th:text=${value}></span>
	<br>
	<br>
	<strong>==================</strong>
</body>
</html>
```
emailをメール本文に表示させているのは、
メールサーバーログインがお問い合わせを受けるメールアドレスのため、自身にメールを送信することになり、お問い合わせの返信をするためのメールアドレスを受信メールから特定できないためです。

sendContactメソッドで
```sendCotanct()
msg.setFrom(email); // 一応セットしなくてもメール送信できる
```
と必要もないのに実装しているのは、
出来ればメール本文にメールアドレスを書きたくなかったという葛藤を表すために残しています。。(どこかで残す意味がある話を聞いたことあるような…？)

今回はControllerへメール本文の内容を書くのはカッコ悪い(表示関連はHTML側に任せたい)と思ったので、メールテンプレートを使用するためのメソッドを導入していますが、気にならないよーという場合はmsg.setText("{メール本文}");でもっと簡潔に書くことができます。

以上をまるまる使っていただければ
簡潔なお問い合わせフォームが作れるかと思います。(お問い合わせメールを送信するための画面はご自身でご自由に作成ください)


# 実装を終えての所感
実装は構想と調査に8割以上の時間を持っていかれました。

まだ右も左もよく分かっていないプログラマーなので、仕方のないことですがやはり悔しい。
もっとサクッと実装出来るつもりでしたが、細かいところの仕様理解が上手くできずにすごく時間をかけてしまいました。

1人で改修しているとどの上司に質問するのかを決めるところからスタートなので、より質問する気が失せます。

でも分からないから立ち往生。
最終的には相談に乗ってくださった上司の手助けもあり、無事実装までこぎつけることができました。

日頃思っていることですが、プログラミングはいかに質問しやすい環境でやるかがとても大事ということに尽きるなということを今回さらに思い知らされました。

その方からセキュリティ面のアドバイスもたくさん頂いたので、セキュリティ面も考慮した実装も同時にしていきたいです。

なにはともあれ、ユーザーに使いやすい、お問い合わせしやすいと思っていただけるようなフォームにしつつも、こちらとしても管理しやすい、拡張性のある実装を心がけていきたいですね。

ここまで読んでいただき、
本当にありがとうございました。