簡化 JDBC 與 Mail


在〈DataSource 設置〉中曾經看過,Spring Boot 可以自動注入 JdbcTemplate,這也可以用來簡化 gossip 中的 AccountDAOJdbcImpl 以及 MessageDAOJdbcImpl,後者其實就是〈DataSource 設置〉看過的程式碼,至於前者,可以如下修改:

package cc.openhome.model;

...略

@Repository
public class AccountDAOJdbcImpl implements AccountDAO {   
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void createAccount(Account acct) {
        jdbcTemplate.update("INSERT INTO t_account(name, email, password, enabled) VALUES(?, ?, ?, 0)", 
                acct.getName(), acct.getEmail(), acct.getPassword());
        jdbcTemplate.update("INSERT INTO t_account_role(name, role) VALUES(?, 'ROLE_MEMBER')", 
                acct.getName());
    }

    @Override
    public Optional<Account> accountByUsername(String name) {
        return jdbcTemplate.queryForList("SELECT * FROM t_account WHERE name = ?", name)
                            .stream()
                            .findFirst()
                            .map(row -> {
                               return new Account(
                                       row.get("NAME").toString(),
                                       row.get("EMAIL").toString(),
                                       row.get("PASSWORD").toString()
                                   );
                            });
    }

    @Override
    public Optional<Account> accountByEmail(String email) {
        return jdbcTemplate.queryForList("SELECT * FROM t_account WHERE email = ?", email)
                .stream()
                .findFirst()
                .map(row -> {
                   return new Account(
                           row.get("NAME").toString(),
                           row.get("EMAIL").toString(),
                           row.get("PASSWORD").toString()
                       );
                });
    }

    public void activateAccount(Account acct) {
        jdbcTemplate.update("UPDATE t_account SET enabled = ? WHERE name = ?", 1, acct.getName());
    }

    @Override
    public void updatePassword(String name, String password) {
        jdbcTemplate.update("UPDATE t_account SET password = ? WHERE name = ?", password, name);
    }
}

舊版文件中看過 Spring 對 Mail 的簡化,提供了 MailSender 等 API,這也可以用來簡化 GmailService,你只要在 application.properties 中刪掉原本自行設置的 Mail 等屬性並設置 spring.mail 等資訊:

spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=yourname@gmail.com
spring.mail.password=yourpassword
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true

就可以把 GmailService 簡化如下:

package cc.openhome.model;

...略

@Service
public class GmailService implements EmailService {
    @Autowired
    private JavaMailSender mailSender;

    @Override
    public void validationLink(Account acct) {
        String link = String.format(
            "http://localhost:8080/verify?email=%s&token=%s", 
             uriEncode(acct.getEmail()), uriEncode(acct.getPassword())
        );

        String anchor = String.format("<a href='%s'>驗證郵件</a>", link);
        String html = String.format("請按 %s 啟用帳戶或複製鏈結至網址列:<br><br> %s", anchor, link);

        sendMessage(acct.getEmail(), "Gossip 註冊結果", html);
    }

    @Override
    public void failedRegistration(String acctName, String acctEmail) {
        sendMessage(
            acctEmail, 
            "Gossip 註冊結果", 
            String.format("帳戶申請失敗,使用者名稱 %s 或郵件 %s 已存在!", acctName, acctEmail)
        );
    }


    @Override
    public void passwordResetLink(Account acct) {
        String link = String.format(
            "http://localhost:8080/reset_password?name=%s&email=%s&token=%s", 
            uriEncode(acct.getName()), 
            uriEncode(acct.getEmail()), 
            uriEncode(acct.getPassword())
        );

        String anchor = String.format("<a href='%s'>重設密碼</a>", link);
        String html = String.format("請按 %s 或複製鏈結至網址列:<br><br> %s", anchor, link);

        sendMessage(
            acct.getEmail(), 
            "Gossip 重設密碼", 
            html
        );            
    }  

    private void sendMessage(String to, String subject, String text)  {
        MimeMessage message = mailSender.createMimeMessage();
        try {
            MimeMessageHelper helper = new MimeMessageHelper(message, false);
            message.setContent(text, "text/html; charset=UTF-8");
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setSentDate(new Date());
            mailSender.send(message);
        } catch (MessagingException e) {
            throw new RuntimeException(e);
        }
    }


    private String uriEncode(String text) {
        try {
            return URLEncoder.encode(text, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        } 
    }
}

你可以在 gossip 中找到以上的範例專案。