m_shige1979のときどきITブログ

プログラムの勉強をしながら学習したことや経験したことをぼそぼそと書いていきます

Github(変なおっさんの顔でるので気をつけてね)

https://github.com/mshige1979

Spring Bootでメールを送信する

Spring Bootでメールを送信する

今回の送信は簡単な平分を送信するだけ

メールサーバ

Gmailをリレーする

設定

application.properties
# port
server.port=7777

# mail
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=Gmailのメールアドレス
spring.mail.password=Gmailのパスワード
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true

※Spring Bootではここに値を設定する

pom.xml

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-freemarker</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-mail</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web-services</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

メール送信

MailSampleController.java
package com.example.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MailSampleController {
	
	private final JavaMailSender javaMailSender;

	@Autowired
	MailSampleController(JavaMailSender javaMailSender) {
		this.javaMailSender = javaMailSender;
	}
	
	@RequestMapping(value = "/mail/send", method = {RequestMethod.POST} )
	public String send() {
		
		SimpleMailMessage mailMessage = new SimpleMailMessage();

	    mailMessage.setTo("送信先のメールアドレス");
	    mailMessage.setReplyTo("リプライのメールアドレス");
	    mailMessage.setFrom("Fromのメールアドレス");
	    mailMessage.setSubject("テストメール");
	    mailMessage.setText("テストメールです、\nここから次の行\nおわりです\n");

	    javaMailSender.send(mailMessage);

		return "ok";
	}
	
}

このapiを叩けばメールを送信する。

結果

f:id:m_shige1979:20170107072733p:plain

所感

メール送信自体は今までなんどもあったけどローカルの開発環境で送信する手段があまり準備されていないことが多くて準備にドタバタしている感じになってしまう。テストとかする場合はもあるけどとりあえずはこれで…

MacよりGmailへメールを送信

備忘録

メッセージのソースだけだとあまりわからないので…

Postfix設定

設定ファイルバックアップ
sudo cp -p /etc/postfix/main.cf /etc/postfix/main.cf.org
/etc/postfix/main.cf
# リレーホストを追加
relayhost = [smtp.gmail.com]:587

#sasl setting
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_sasl_tls_security_options = noanonymous
smtp_sasl_mechanism_filter = plain

#tls setting
smtp_use_tls = yes

※末尾に追加

/etc/postfix/sasl_passwd
[smtp.gmail.com]:587 ユーザーID@gmail.com:パスワード

※ユーザーIDとパスワードはそれぞれ任意のものを設定
※2段階認証している場合はアプリパスワードを発行すること

再作成
sudo chmod 640 /etc/postfix/sasl_passwd
sudo postmap /etc/postfix/sasl_passwd
再読込
sudo postfix reload

テスト

echo hogehoge | mail ユーザーID@gmail.com

macのpostfixでローカルメールを扱う

eclipseなどで開発する場合は

メールサーバの有無で開発環境に問題が発生しやすい
WindowsMacなどでもメールサーバは未実装の環境が多いので外部にはメールを送信したくないことある

基本的には

m-shige1979.hatenablog.com
これです

postfix

存在チェック
$ which postfix
/usr/sbin/postfix
$

あるのでそのまま

起動
sudo postfix start
設定ファイルバックアップ
sudo cp -p /etc/postfix/main.cf /etc/postfix/main.cf.org
/etc/postfix/main.cf編集
# 全てのメールを受け取る
inet_interfaces = all

# ローカル配送で不明なユーザを拒否しない
local_recipient_maps =

# Maildir形式として保存するディレクトリを /usr/local/mail/ の下にユーザー毎に作成する
mail_spool_directory = /usr/local/mail/

# ローカル配送で不明なユーザへのメールは maildev へ送る
luser_relay = maildev

# トランスポートマップを指定
transport_maps = hash:/etc/postfix/transport

※末尾に追加

/etc/postfix/transport
*       local:

※末尾に追加

postmapで作成
sudo postmap /etc/postfix/transport
メール保存先を準備
sudo mkdir -p /usr/local/mail/
sudo chmod 777 /usr/local/mail/
ユーザーを作成
sudo dscl . -create /Groups/users gid 1001
sudo dscl . -create /Users/maildev
sudo dscl . -create /Users/maildev RealName maildev
sudo dscl . -create /Users/maildev UniqueID 1001
sudo dscl . -create /Users/maildev PrimaryGroupID 1001
sudo dscl . -create /Users/maildev NFSHomeDirectory /Users/maildev
sudo dscl . -create /Users/maildev UserShell /bin/bash
sudo passwd maildev
sudo createhomedir -b -u maildev

※500番台を使用するとシステムが使えなくなる恐れがあるので気をつける必要があります。

リロード
sudo postfix reload

dovecot確認

brew install
$ brew install dovecot
READMEを確認
$ cat /usr/local/etc/dovecot/README
Configuration files go to this directory. See example configuration files in
/usr/local/Cellar/dovecot/2.2.27/share/doc/dovecot/example-config/
$
移動
cp /usr/local/Cellar/dovecot/2.2.27/share/doc/dovecot/example-config/dovecot.conf /usr/local/etc/dovecot/
cp -r /usr/local/Cellar/dovecot/2.2.27/share/doc/dovecot/example-config/conf.d /usr/local/etc/dovecot/
起動
sudo brew services start dovecot
/usr/local/etc/dovecot/dovecot.conf
protocols = imap pop3
mail_location = maildir:/usr/local/mail/%u
再起動
sudo brew services restart dovecot

テスト

$ telnet localhost 25
Trying ::1...
Connected to localhost.
Escape character is '^]'.
220 matsumotoshigejinoMac-mini.local ESMTP Postfix
helo localhost
250 matsumotoshigejinoMac-mini.local
mail from: aaaa@test1.com
250 2.1.0 Ok
rcpt to: bbb@hoge.com
250 2.1.5 Ok
data
354 End data with <CR><LF>.<CR><LF>
subject: test2
aaaaaa
ssssssssss
ddddd
.
250 2.0.0 Ok: queued as 5AA2D4F6580
quit
221 2.0.0 Bye
Connection closed by foreign host.
$
メール確認
$ ll /usr/local/mail/maildev/new/
total 8
-rw-------  1 maildev  601  443  1  7 02:21 1483723300.V1000008I4f6595M15495.matsumotoshigejinoMac-mini.local
$
ソース内容
Return-Path: <aaaa@test1.com>
X-Original-To: bbb@hoge.com
Delivered-To: bbb@hoge.com
Received: from localhost (localhost [IPv6:::1])
	by matsumotoshigejinoMac-mini.local (Postfix) with SMTP id 5AA2D4F6580
	for <bbb@hoge.com>; Sat,  7 Jan 2017 02:21:02 +0900 (JST)
subject: test2
Message-Id: <20170106172111.5AA2D4F6580@matsumotoshigejinoMac-mini.local>
Date: Sat,  7 Jan 2017 02:21:02 +0900 (JST)
From: aaaa@test1.com

aaaaaa
ssssssssss
ddddd

こんな感じ?

Windows10で開発用のSMTPサーバを使いたい

環境

Windows10(VM

設定

smtp4dev 2.0.9 standaloneをダウンロード

f:id:m_shige1979:20170107002828j:plain

ダウンロードしたファイルを解凍して起動する

f:id:m_shige1979:20170107003033j:plain

Windowsの機能が足りない場合は追加する

f:id:m_shige1979:20170107003102j:plain

ファイアウォールを解除する

f:id:m_shige1979:20170107003128j:plain

起動を確認

f:id:m_shige1979:20170107003152j:plain

メール送信

JavaMailで送信する
package maven_test01;

import java.util.Date;
import java.util.Properties;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

public class Sample01 {

    public static void main(String[] args) throws AddressException, MessagingException {

        Properties props = new Properties();
	
        //smptサーバに関する設定
        props.setProperty("mail.smtp.host", "localhost");
        props.setProperty("mail.smtp.port", "25");
        Session session = Session.getInstance(props, null);
        session.setDebug(true);
        MimeMessage msg = new MimeMessage(session);
        
        // 送信データ設定
        msg.setFrom(new InternetAddress("test@example.com"));
        InternetAddress[] address = {new InternetAddress("hoge@example.com")};
        msg.setRecipients(Message.RecipientType.TO, address);
        msg.setSubject("JavaMail APIs Test");
        msg.setSentDate(new Date());
        msg.setText("hogehoge");
        
        // 送信
        Transport.send(msg);

    }

}

ツール確認

追加されていること

f:id:m_shige1979:20170107003441j:plain

メーラーで確認する

f:id:m_shige1979:20170107003501j:plain

所感

基本的にはVMPlayerとかでLinuxサーバ立ててやりたかったけど開発環境をWindowsだけで完結させたい場合はこれでもいいかも。
まあ、受信サーバを使う必要ないし…

Javaで動的にメソッドを呼び出す

動的にメソッドを呼び出す場合

PHPとかでは変数にメソッド名を設定すれば簡単に使用できるけどJavaの場合はリフレクションの機能を駆使する必要がある感じのよう
今回はクラスは全部同じものとして扱う。実際はパッケージは変わる可能性があるかも…

呼び出されるメソッドのクラス

Sample02.java
package sample_method01;

import sample_method01.data.Item;

public class Sample02 {
	
	public void test1() {
		System.out.println("test1");
	}
	public void test2(String str) {
		System.out.println("test2" + " " + str);
	}
	public void test3(Integer val) {
		System.out.println("test3" + " " + val);
	}
	public void test4(Item obj) {
		System.out.println("test4" + " " + obj.getName() + " " + obj.getAge());
	}

}

引数にクラスを指定する用

Item.java
package sample_method01.data;

public class Item {
	private String name;
	private Integer age;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	
	
}

呼び出し先

Sample01.java
package sample_method01;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import sample_method01.data.Item;

public class Sample01 {

	public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
		
		Sample02 obj = new Sample02();
		Method method;
		
		// 引数なし
		method = obj.getClass().getMethod("test1");
		method.invoke(obj);
		
		// 引数に1つの文字列
		method = obj.getClass().getMethod("test2", new Class[]{String.class});
		method.invoke(obj, "aaaa");
		
		// 引数に1つの数値
		method = obj.getClass().getMethod("test3", new Class[]{Integer.class});
		method.invoke(obj, 100);
		
		// 引数に1つのクラス
		Item item = new Item();
		item.setName("hoge");
		item.setAge(30);
		method = obj.getClass().getMethod("test4", new Class[]{Item.class});
		method.invoke(obj, item);
	}

}


んで実行

test1
test2 aaaa
test3 100
test4 hoge 30

※戻り値の取得は基本的にはObject型なのでCastする必要があるはず…

今回はここまで

Spring BootのRestControllerで簡易テスト

テスト

目視チェックしか基本したことない
だって作成するの面倒なんだもん

テストサンプル

ItemControllerTest.java
package com.example.controller;

import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;

import com.example.AbstractApiControllerTest;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
@WebAppConfiguration
public class ItemControllerTest extends AbstractApiControllerTest {
	
    /**
     * /item/detail/1
     * @throws Exception
     */
    @Test
    public void testSampleTest1() throws Exception {
    	
    	Integer id;
    	id = 1;
    	
    	// 実行し、値を検証
        mvc.perform(get("/item/detail/{id}", id))
            .andExpect(status().isOk())
            .andExpect(content().contentType(APPLICATION_JSON_UTF8))
            .andExpect(jsonPath("id", is(1)))
            .andExpect(jsonPath("name", is("apple")))
            .andExpect(jsonPath("price", is(120)))
            .andExpect(jsonPath("createAt", is(Long.valueOf("1483461861000"))))
            .andExpect(jsonPath("updateAt", is(Long.valueOf("1483461861000"))));
    }
    
    /**
     * /item/list
     * @throws Exception
     */
    @Test
    public void testSampleTest2() throws Exception {
    	
    	// 実行し、値を検証
        mvc.perform(get("/item/list"))
            .andExpect(status().isOk())
            .andExpect(content().contentType(APPLICATION_JSON_UTF8))
            .andExpect(jsonPath("$[0].id", is(1)))
            .andExpect(jsonPath("$[0].name", is("apple")))
            .andExpect(jsonPath("$[0].price", is(120)))
            .andExpect(jsonPath("$[0].createAt", is(Long.valueOf("1483461861000"))))
            .andExpect(jsonPath("$[0].updateAt", is(Long.valueOf("1483461861000"))))
            .andExpect(jsonPath("$[1].id", is(2)))
            .andExpect(jsonPath("$[1].name", is("orange")))
            .andExpect(jsonPath("$[1].price", is(150)))
            .andExpect(jsonPath("$[1].createAt", is(Long.valueOf("1483461861000"))))
            .andExpect(jsonPath("$[1].updateAt", is(Long.valueOf("1483461861000"))))
            .andExpect(jsonPath("$[2].id", is(3)))
            .andExpect(jsonPath("$[2].name", is("grape")))
            .andExpect(jsonPath("$[2].price", is(200)))
            .andExpect(jsonPath("$[2].createAt", is(Long.valueOf("1483461861000"))))
            .andExpect(jsonPath("$[2].updateAt", is(Long.valueOf("1483461861000"))));
    }
	
}

jsonPath

RestAPIの場合はjson形式で返却することが多いのでjsonPathでjsonの項目をチェックする。

  • 直下の項目の場合は項目名を一致
  • データ型で数値と文字で変わるのでチェックする必要がある

所感

今回はrest apiの参照テストのみなので登録時やステータスコードなどのチェックも考慮して見る必要がある。

doma2を使用したin句の条件設定

完全一致はあるけど

他の方法ってなかなか見つからないです

IN句による条件指定

select
	*
from
	keiyaku
where 
	name in ('tanaka', 'inoue')

こんなやつ

Dao

KeiyakuDao.java
package com.example.dao;

import java.util.List;

import org.seasar.doma.Dao;
import org.seasar.doma.Select;

import com.example.config.AppConfig;
import com.example.entity.Keiyaku;
import com.example.entity.KeiyakuItem;

@Dao(config = AppConfig.class)
public interface KeiyakuDao {
	
	@Select
	List<Keiyaku> findNamesSearchAll(List<String> names);
	
}

※引数にList形式で設定する

SQL

findNamesSearchAll.sql
select
	*
from
	keiyaku
where
	where name in /* names */('aaa', 'bbb', 'ccc')

※inを指定するが別に特別な記載はしない

実行

Sample01.java
package com.example;

import java.util.Arrays;
import java.util.List;

import org.seasar.doma.jdbc.tx.TransactionManager;

import com.example.config.AppConfig;
import com.example.dao.KeiyakuDao;
import com.example.dao.KeiyakuDaoImpl;
import com.example.entity.Keiyaku;
import com.example.entity.KeiyakuItem;

public class Sample01 {

	public static void main(String[] args) {
		
		TransactionManager tm = AppConfig.singleton().getTransactionManager();
		tm.required(new Runnable(){

			@Override
			public void run() {
				KeiyakuDao dao = new KeiyakuDaoImpl();
				
				List<Keiyaku> list;
				list = dao.findNamesSearchAll(Arrays.asList("tanaka", "inoue"));	
			}		
		});
	}
}

1 04, 2017 11:35:44 午前 org.seasar.doma.jdbc.tx.LocalTransaction begin
情報: [DOMA2063] ローカルトランザクション[1190654826]を開始しました。
1 04, 2017 11:35:44 午前 com.example.dao.KeiyakuDaoImpl findNamesSearchAll
情報: [DOMA2220] ENTER  : クラス=[com.example.dao.KeiyakuDaoImpl], メソッド=[findNamesSearchAll]
1 04, 2017 11:35:44 午前 com.example.dao.KeiyakuDaoImpl findNamesSearchAll
情報: [DOMA2076] SQLログ : SQLファイル=[META-INF/com/example/dao/KeiyakuDao/findNamesSearchAll.sql],
select
	*
from
	keiyaku
where name in ('tanaka', 'inoue')
1 04, 2017 11:35:44 午前 com.example.dao.KeiyakuDaoImpl findNamesSearchAll
情報: [DOMA2221] EXIT   : クラス=[com.example.dao.KeiyakuDaoImpl], メソッド=[findNamesSearchAll]
1 04, 2017 11:35:44 午前 org.seasar.doma.jdbc.tx.LocalTransaction commit
情報: [DOMA2067] ローカルトランザクション[1190654826]をコミットしました。
1 04, 2017 11:35:44 午前 org.seasar.doma.jdbc.tx.LocalTransaction commit
情報: [DOMA2064] ローカルトランザクション[1190654826]を終了しました。


まあ、こんな感じですかね…