Ch10-3 : Sigup

[Signup]
這個小節,會詳細說明註冊功能怎麼實作出來的,我把註冊的動作,拆解成七個部份功能,依序做好這七個部份功能後,我們就可以完成註冊功能了。若解釋的過程中,還是不太明白,建議實際針對每個步驟去操作,理解每個步驟的運作流程,就會更加印象深刻了。

  • Step 1 : 取得表單註冊資料,若錯誤,回到註冊頁面,警告錯誤訊息。
  • Step 2 : 進行表單驗證,是否正確。若錯誤,回到註冊頁面顯示錯誤訊息。
  • Step 3 : 檢核通過,新增會員資料,且尚未認證。
  • Step 4 : 註冊新增成功,新增認證連結資料。
  • Step 5 : 新增會員記錄檔。
  • Step 6 : 進行寄送認證信動作。
  • Step 7 : 以上都順利完成,導入成功註冊頁面。

網址 : https://github.com/loveu8/playMyBtaisMariaDB/blob/Ch10/app/controllers/WebController.java


Step 0
首先,我們先把上個章節,已經擺好的網頁與檔案,拿出來檢視註冊功能的畫面是怎樣完成。

app/views/web/headerLibs.scala.html
之後會有許多頁面會需要到這些cssjs,都需要引用到,特別獨立成Libary頁面。

<!-- JS lib-->
<script src="/assets/javascripts/jquery-3.1.0.min.js"></script>
<script src="/assets/javascripts/jquery.dropotron.min.js"></script>
<script src="/assets/javascripts/skel.min.js"></script>
<script src="/assets/javascripts/skel-viewport.min.js"></script>
<script src="/assets/javascripts/util.js"></script>
<script src="/assets/javascripts/main.js"></script>

<!-- 全域樣式 -->
<link rel="stylesheet" href="/assets/stylesheets/main.css" />
<link rel="stylesheet" href="/assets/stylesheets/normalize.css">


/app/views/web/loginSignup/loginSignupLibs.scala.html
註冊與登入的css檔案。

<link rel="stylesheet" href='@routes.Assets.versioned("stylesheets/loginSignup.css")'>


app/views/web/headerNav.scala.html
Header上方的選單列表,因為很經常被各個頁面使用到,也獨立出來,方面之後其它頁面引用。

<!-- Nav -->
<nav id="nav">
    <ul>
        <li id="nav_index"><a href="@controllers.routes.WebController.index.url">首頁</a></li>
        <li id="nav_user">
            <a href="#">我的世界</a>
            <ul>
                <li><a href="@controllers.routes.WebController.login.url">登入</a></li>
            </ul>
        </li>
        <li id="nav_signup">
            <a href="@controllers.routes.WebController.signup.url">註冊</a>
            <ul>
                <li><a href="@controllers.routes.WebController.resendAuthEmail.url">重發認證信</a></li>
            </ul>
        </li>
    </ul>
</nav>
<script>
    <!-- 來偵測,外面的Div被誰使用到,而去增加class current屬性-->
    $(document).ready(function() {
      if ( $('#select_nav_index').length ){
          $('#nav_index').addClass('current');
      }
      if ( $('#select_nav_user').length ){
          $('#nav_user').addClass('current');
      }
      if ( $('#select_nav_signup').length ){
          $('#nav_signup').addClass('current');
      }
    });
</script>
<a href="index.html" id="logo">Star</a>


conf/routes

# http://127.0.0.1:9000/web/signup
GET        /web/signup                  controllers.WebController.signup()

# http://127.0.0.1:9000/web/signupOk (測試用)
GET        /web/signupOk              controllers.WebController.signupOk()

# http://127.0.0.1:9000/web/signup
POST    /web/signup                  controllers.WebController.goToSignup()


app/views/web/loginSignup/signup.scala.html
註冊頁面。

<!DOCTYPE html>
<html >
  <head>
    <meta charset="UTF-8">
    <title>註冊</title>
    @*利用Play Scala的小老鼠符號,來去引用相關頁面*@
    @views.html.web.headerLibs()

    @views.html.web.loginSignup.loginSignupLibs()

  </head>

  <body>

    <div id="page-wrapper">
        <div id="select_nav_signup">@views.html.web.headerNav()</div>
    </div>
    <div class="form">

      <ul class="tab-group">
        <li class="tab active"><a href="#signup">註冊</a></li>
      </ul>

      <div class="tab-content"> 
        <div id="signup">   
            <form action="@controllers.routes.WebController.goToSignup.url" method="post" id="signupForm">
            <div class="field-wrap">
                <label class="lable-field-wrap">使用者名稱</label>
                <input type="text" required autocomplete="off" name="username"/>
                @if(flash.containsKey("username")) {
                    <span style="color:red;">@flash.get("username")</span>
                }
            </div>
            <div class="field-wrap">
                <label class="lable-field-wrap">電子信箱</label>
                <input type="email" required autocomplete="off" name="email"/>
                @if(flash.containsKey("email")) {
                    <span style="color:red;">@flash.get("email")</span>
                }
            </div>
            <div class="field-wrap">
                <label class="lable-field-wrap">密碼</label>
                <input type="password" required autocomplete="off" name="password"/>
            </div>
            <div class="field-wrap">
                <label class="lable-field-wrap">確認密碼</label>
                <input type="password" required autocomplete="off" name="retypePassword"/>
                @if(flash.containsKey("password")) {
                    <span style="color:red;">@flash.get("password")</span>
                }
                @if(flash.containsKey("signupError")) {
                    <span style="color:red;">@flash.get("signupError")</span>
                }
            </div>
            <a>
                <input type="submit" value="Signup" class="button button-block">
            </a>
            </form>
          </div> 
        </div><!-- tab-content -->   
    </div> <!-- /form -->
    <div id="titleBar"></div>
    <script src='@routes.Assets.versioned("javascripts/loginSignup.js")'></script>
    <script>
        $('#signupForm')[0].reset();
        @if(flash.containsKey("errorForm")) {
            alert('@flash.get("errorForm")');
        }
    </script>
  </body>
</html>


/app/views/web/loginSignup/signupOk.scala.html
註冊成功畫面。

<!DOCTYPE html>
<html >
  <head>
    <meta charset="UTF-8">
    <title>註冊成功</title>

    @views.html.web.headerLibs()

    @views.html.web.loginSignup.loginSignupLibs()


  </head>

  <body>
    <div id="page-wrapper">
        <div id="select_nav_signup">@views.html.web.headerNav()</div>
    </div>
    <div class="form">

      <ul class="tab-group">
        <li class="tab active"><a href="#">註冊成功</a></li>
      </ul>

      <div class="tab-content"> 
        <div id="signup">   
            <span>您已註冊成功,請至您申請的信箱,收取認證信件,認證有效時間為24小時內,謝謝。</span>
        </div> 
      </div><!-- tab-content -->   
    </div> <!-- /form -->
    <div id="titleBar"></div>
  </body>
</html>


參考畫面
網址 : http://127.0.0.1:9000/web/signup

接下來回到我們的WebController,註冊會員是這個以下說明流程順序,下面會依序講解註冊步驟。
path : /app/controllers/WebController.java

  /**
   * <pre>
   * 進行註冊
   * 
   * Step 1 : 取得表單註冊資料,若錯誤,回到註冊頁面,警告錯誤訊息。
   * Step 2 : 進行表單驗證,是否正確。若錯誤,回到註冊頁面顯示錯誤訊息。
   * Step 3 : 檢核通過,新增會員資料,且尚未認證。
   * Step 4 : 註冊新增成功,新增認證連結資料。
   * Step 5 : 新增會員記錄檔。
   * Step 6 : 進行寄送認證信動作。
   * Step 7 : 以上都順利完成,導入成功註冊頁面。
   * 
   * </pre>
   */
  public Result goToSignup() {

    // 清除暫存錯誤訊息
    flash().clear();

    // Step 1
    SignupRequest request = this.getSignupRequest();
    if (request == null) {
      flash().put("errorForm","註冊資料錯誤,請重新嘗試!!");
      return ok(signup.render());
    }

    // Step 2
    Map<String, VerificFormMessage> verificInfo = this.checkSingupRequest(request);
    for (String key : verificInfo.keySet()) {
      // 發現驗證沒過,放入錯誤訊息
      if (!"200".equals(verificInfo.get(key).getStatus())) {
        flash().put(key, verificInfo.get(key).getStatusDesc());
      }
    }
    if(!flash().isEmpty()){
      return ok(signup.render());
    }

    try {
      // Step 3
      int isSignupOk = webService.signupNewMember(request);
      if(isSignupOk == 0){
        flash().put("signupError", "註冊會員失敗,請重新註冊,謝謝。");
        return ok(signup.render());
      }

      // Step 4
      Utils_Signup utils_Signup = new Utils_Signup();
      Member newMember = webService.findMemberByEmail(request.getEmail());
      String signupAuthString = utils_Signup.genSignupAuthString(newMember.getEmail());

      Map<String , String> memberToken = new HashMap<String , String>();
      memberToken.put("memberNo", newMember.getMemberNo());
      memberToken.put("tokenString", signupAuthString);
      memberToken.put("type", MemberTokenType.Signup.toString());
      int isSingAuthStringOk = webService.genSignupAuthData(memberToken);

      // Step 5
      Map<String , String> memberLoginData 
          = utils_Signup.genMemberLoginData(newMember.getMemberNo() ,
                                            "PC" , 
                                            request().remoteAddress() , 
                                            MemberLoginStatus.S1.getStatus());
      int isMemberLoginLogOk = webService.genMemberLoginLog(memberLoginData);

      // Step 6
      Utils_Email utils_Email = new Utils_Email();
      Email email = utils_Email.genSinupAuthEmail(newMember, signupAuthString);
      boolean isSeadMailOk = utils_Email.sendMail(email);

      // Step 7
      if(isSingAuthStringOk > 0 && isMemberLoginLogOk > 0 && isSeadMailOk ){
        return ok(signupOk.render());
      } else {
        flash().put("signupError", "Opss...寄送認證信件發生錯誤,請使用重發認證信功能,完成認證動作,謝謝。");
        return ok(signup.render());
      }
    } catch (Exception e) {
      e.printStackTrace();
      flash().put("signupError", "註冊會員失敗,請重新註冊,謝謝。");
      return ok(signup.render());
    }
  }


  // Step 1 : 取得註冊資訊請求
  private SignupRequest getSignupRequest() {
    SignupRequest request = null;
    try {
      request = formFactory.form(SignupRequest.class).bindFromRequest().get();
      Logger.info("before , new member request data = " + Json.toJson(request));
    } catch (Exception e) {
      Logger.error("表單內容非註冊資訊,轉換類別錯誤,回傳空物件");
    }
    return request;
  }


  // Step 2 : 檢查註冊資訊
  private Map<String, VerificFormMessage> checkSingupRequest(SignupRequest request) {

    boolean isRegEmail = true;
    boolean isUsedUsername = true;
    try{
      isRegEmail = webService.checkMemberByEmail(request.getEmail());
      isUsedUsername = webService.checkMemberByUsername(request.getUsername());
    } catch(Exception e){
      e.printStackTrace();
    }
    Map<String, VerificFormMessage> verificInfo 
        = new Utils_Signup().checkSingupRequest(request , isRegEmail , isUsedUsername);
    Logger.info("verificInfo = " + Json.toJson(verificInfo));
    return verificInfo;
  }



Step 1 : 取得表單註冊資料,若錯誤,回到註冊頁面,警告錯誤訊息。
會需要表單填寫的原因是,可能會有不正當的使用者,想利用傳送表單時,塞入不屬於這個表單的內容,嘗試攻擊網站,我們需要針對註冊申請表單做檢核,確認是否市申請註冊的表單資料。
path : /app/controllers/WebController.java

...
public Result goToSignup() {

  // 清除暫存錯誤訊息
  flash().clear();

  // Step 1
  SignupRequest request = this.getSignupRequest();
  if (request == null) {
    flash().put("errorForm","註冊資料錯誤,請重新嘗試!!");
    return ok(signup.render());
  }
  ...
}

// Step 1 : 取得註冊資訊請求
private SignupRequest getSignupRequest() {
  SignupRequest request = null;
  try {
    request = formFactory.form(SignupRequest.class).bindFromRequest().get();
    Logger.info("before , new member request data = " + Json.toJson(request));
  } catch (Exception e) {
    Logger.error("表單內容非註冊資訊,轉換類別錯誤,回傳空物件");
  }
  return request;
}


檢查畫面
彈跳警告畫面,主要寫在signup.scala.html裡。當回到原本頁面,flash發現含有errorForm的資料,就會顯示出要給使用者知道的訊息,提醒使用者不要使用不合法資料,攻擊網站。
/app/views/web/loginSignup/signup.scala.html

<script>
    $('#signupForm')[0].reset();
    @if(flash.containsKey("errorForm")) {
        alert('@flash.get("errorForm")');
    }
</script>




Step 2 : 進行表單驗證,是否正確。若錯誤,回到註冊頁面顯示錯誤訊息。
前面確認過使用者輸入的資料,是我們想要的內容後,接下來我們要針對使用者輸入的資料進行驗證。

首先新增表單對應的類別,這樣我們Play就可以對註冊表單進行Form bind,轉換成物件來操作。
path : /app/pojo/web/signup/request/SignupRequest.java

package pojo.web.signup.request;

public class SignupRequest {

  private String email;

  private String username;

  private String password;

  private String retypePassword;


  public void setEmail(String email) {
    this.email = email;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public void setRetypePassword(String retypePassword) {
    this.retypePassword = retypePassword;
  }

  public String getEmail() {
    return email;
  }

  public String getUsername() {
    return username;
  }

  public String getPassword() {
    return password;
  }

  public String getRetypePassword() {
    return retypePassword;
  }

}


path : /app/controllers/WebController.java

@Inject
FormFactory formFactory;

@Inject
private WebService webService;

public Result goToSignup() {

  ...

  // Step 2
  Map<String, VerificFormMessage> verificInfo = this.checkSingupRequest(request);
  for (String key : verificInfo.keySet()) {
    // 發現驗證沒過,放入錯誤訊息
    if (!"200".equals(verificInfo.get(key).getStatus())) {
      flash().put(key, verificInfo.get(key).getStatusDesc());
    }
  }
  if(!flash().isEmpty()){
    return ok(signup.render());
  }
  ...
}

...

// Step 2 : 檢查註冊資訊
private Map<String, VerificFormMessage> checkSingupRequest(SignupRequest request) {

  boolean isRegEmail = true;
  boolean isUsedUsername = true;
  try{
    isRegEmail = webService.checkMemberByEmail(request.getEmail());
    isUsedUsername = webService.checkMemberByUsername(request.getUsername());
  } catch(Exception e){
    e.printStackTrace();
  }
  Map<String, VerificFormMessage> verificInfo 
      = new Utils_Signup().checkSingupRequest(request , isRegEmail , isUsedUsername);
  Logger.info("verificInfo = " + Json.toJson(verificInfo));
  return verificInfo;
}


因為註冊需要跟資料庫做新增刪修的動作,我們需要寫好相關的程式,來紀錄相關資訊。跟之前一樣,需要先建立一個Interface,寫好對資料庫操作的各種行為。
path : /app/services/WebService.java

package services;

import java.util.Map;

import org.apache.ibatis.annotations.Param;

import pojo.web.Member;
import pojo.web.MemberToken;
import pojo.web.signup.request.SignupRequest;

public interface WebService {

  /** 註冊新會員 */
  public int signupNewMember(@Param("signupRequest") SignupRequest signupRequest);

  /** 檢查是否有該會員存在 */
  public boolean checkMemberByEmail(String email);

  /** 檢查是否有該會員使用者名稱是否存在 */
  public boolean checkMemberByUsername(String username);

  /** 用Email 尋找會員資料 */
  public Member findMemberByEmail(String email);

  /** 用會員編號 尋找會員資料 */
  public Member findMemberByMemberNo(String memberNo);

  /** 用Email與使用者名稱 尋找會員資料 */
  public Member findMemberByEmailAndUserName(@Param("email")String email , @Param("username")String username);

  /** 產生會員Token資料  */
  public int genTokenData(@Param("memberToken") Map<String , String> memberToken);

  /** 會員記錄檔  */
  public int genMemberLoginLog(@Param("memberLoginData") Map<String , String> memberLoginData);

  /** 驗證會員連結 */
  public MemberToken getMemberTokenData(@Param("token") String token , @Param("type") String type);

  /** 更新認證連結*/
  public int updateMemberAuth(String memberNo);

  /** 更新會員資料*/
  public int updateMemberToAuthOk(String memberNo);

  /** 新增會員紀錄檔資料 */
  public int genMemberChangeLog(@Param("member") Member member);

}


interface後,我們就需要新增對應的WebService.xml,依照interface的定義,寫好相關的SQL程式。
path : /conf/services/WebService.xml

<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="services.WebService">

    <!-- 註冊會員 -->
    <insert id="signupNewMember" parameterType="pojo.web.signup.request.SignupRequest">
        insert into
        member_main (memberNo ,email , password , username , createDate, modifyDate)
        values
        ( 
            null , 
            #{signupRequest.email} ,
            #{signupRequest.password},
            #{signupRequest.username} ,
            DATE_FORMAT(NOW() , '%Y%m%d%H%i%s') ,
            DATE_FORMAT(NOW(),'%Y%m%d%H%i%s')
        );
    </insert>


    <!-- 檢查是否有重覆註冊的會員 -->
    <select id="checkMemberByEmail" parameterType="String"
        resultType="boolean">
        SELECT count(1) FROM member_main WHERE email=#{email}
    </select>


    <!-- 檢查是否有重覆的使用者名稱 -->
    <select id="checkMemberByUsername" parameterType="String"
        resultType="boolean">
        SELECT count(1) FROM member_main WHERE username=#{username}
    </select>


    <!-- 根據email查詢會員資料 -->
    <select id="findMemberByEmail" parameterType="String"
        resultType="pojo.web.Member">
        SELECT * FROM member_main WHERE email = #{email}
    </select>


    <!-- 根據memberNo查詢會員資料 -->
    <select id="findMemberByMemberNo" parameterType="String"
        resultType="pojo.web.Member">
        SELECT * FROM member_main WHERE memberNo = #{memberNo}
    </select>


    <!-- 根據 email與username查尋會員資料 -->
    <select id="findMemberByEmailAndUserName" parameterType="String"
        resultType="pojo.web.Member">
        SELECT * FROM member_main WHERE email = #{email} AND username =#{username}
    </select>

    <!-- 註冊認證連結 -->
    <insert id="genTokenData" parameterType="Map">
      insert into
      member_token (tokenString, type ,memberNo , sendDate , isUse , createDate , modifyDate , expiryDate)
      values
      ( 
          #{memberToken.tokenString} , 
          #{memberToken.memberNo} ,
          #{memberToken.type} ,
          DATE_FORMAT(NOW() , '%Y%m%d%H%i%s'),
          0,
          DATE_FORMAT(NOW() , '%Y%m%d%H%i%s') ,
          DATE_FORMAT(NOW(),'%Y%m%d%H%i%s') ,
          DATE_FORMAT(DATE_ADD(NOW(),INTERVAL 1 DAY),'%Y%m%d%H%i%s')
      )
    </insert>


    <!-- 會員記錄檔 -->
    <insert id="genMemberLoginLog" parameterType="Map">
        insert into
        member_login_log (memberNo , status , ipAddress , device , loginDate)
        values
        ( 
            #{memberLoginData.memberNo} , 
            #{memberLoginData.status} ,
            #{memberLoginData.ipAddress} ,
            #{memberLoginData.device} ,
            DATE_FORMAT(NOW(),'%Y%m%d%H%i%s')
        );
    </insert>


    <!-- 撈出會員認證資訊 -->
    <select id="getMemberTokenData" parameterType="String" resultType="pojo.web.MemberAuth">
        SELECT *,DATE_FORMAT(NOW(),'%Y%m%d%H%i%s') as dbTime FROM member_auth WHERE authString=#{auth}
    </select>


    <!--  更新會員認證-->
    <update id="updateMemberToken">
        UPDATE member_token SET isUse = 1 , modifyDate = DATE_FORMAT(NOW(),'%Y%m%d%H%i%s')
        WHERE memberNo = #{memberNo} AND isUse = 0 AND type=#{type}
    </update>


    <!--  更新會員資料 -->
    <update id="updateMemberToAuthOk">
        UPDATE member_main SET status = '2' , modifyDate = DATE_FORMAT(NOW(),'%Y%m%d%H%i%s')
        WHERE memberNo = #{memberNo}
    </update>


    <!-- 會員記錄檔 -->
    <insert id="genMemberChangeLog" parameterType="pojo.web.Member">
        insert into
        member_main_log(memberNo , email , password , username , createDate)
        values
        ( 
            #{member.memberNo} , 
            #{member.email} ,
            #{member.password},
            #{member.username} ,
            DATE_FORMAT(NOW(), '%Y%m%d%H%i%s')
        );
    </insert>


</mapper>


前面兩個檔案就緒之後,接下來調整MyBatisModule.java,把WebService.java加入進去,這樣我們Play就可以跟MariaDB互動了。
path : /app/modules/MyBatisModule.java

...
  @Override
  protected void initialize() {
    environmentId("default");
    bindConstant().annotatedWith(Names.named("mybatis.configuration.failFast")).to(true);
    bindDataSourceProviderType(PlayDataSourceProvider.class);
    bindTransactionFactoryType(JdbcTransactionFactory.class);
    // 把我們要呼叫DB的類別,增加到MapperClass
    addMapperClass(UserService.class);
    addMapperClass(WebService.class);
  }
....


前面完成後,我們新增一隻Utils_Signup來檢查申請註冊的資料。
path : /app/utils/signup/Utils_Signup.java

package utils.signup;

import java.security.MessageDigest;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import play.libs.Json;
import pojo.web.signup.request.SignupRequest;
import pojo.web.signup.status.EmailStatus;
import pojo.web.signup.status.PasswordStatus;
import pojo.web.signup.status.UsernameStatus;
import pojo.web.signup.verific.VerificFormMessage;


public class Utils_Signup {


    public Map<String , VerificFormMessage>checkSingupRequest(SignupRequest reqeuest , boolean isRegEmail , boolean isUsedUsername){

        Map<String , VerificFormMessage> info = new HashMap<String , VerificFormMessage>();

        String email             = reqeuest.getEmail();
        String username            = reqeuest.getUsername();
        String password            = reqeuest.getPassword();
        String retypepassword   = reqeuest.getRetypePassword();

        info.put("email", checkEmail(email , isRegEmail));
        info.put("username", checkUsername(username , isUsedUsername));
        info.put("password", checkPassword(password , retypepassword));

        return info;
    }


    public VerificFormMessage checkEmail(String email , boolean isRegEmail){
        VerificFormMessage message = new VerificFormMessage();

        message.setInputName("email");

        String emailRegex = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$";

        if("".equals(email) || email == null){
            message.setStatus(EmailStatus.S1.status);
            message.setStatusDesc(EmailStatus.S1.statusDesc);
        } else if(! email.matches(emailRegex)){
            message.setStatus(EmailStatus.S2.status);
            message.setStatusDesc(EmailStatus.S2.statusDesc);
        } else if(isRegEmail){ 
            message.setStatus(EmailStatus.S3.status);
            message.setStatusDesc(EmailStatus.S3.statusDesc);
        } else {
            message.setStatus(EmailStatus.S200.status);
            message.setStatusDesc(EmailStatus.S200.statusDesc);
        }

        play.Logger.info("checkEmail = " + Json.toJson(message));

        return message;
    }


    private VerificFormMessage checkUsername(String username , boolean isUsedUsername) {
        VerificFormMessage message = new VerificFormMessage();

        message.setInputName("username");

        String usernameRegex = "^[a-zA-Z]{4,15}$";

        if("".equals(username) || username == null){
            message.setStatus(UsernameStatus.S1.status);
            message.setStatusDesc(UsernameStatus.S1.statusDesc);
        } else if(!username.matches(usernameRegex)){
            message.setStatus(UsernameStatus.S2.status);
            message.setStatusDesc(UsernameStatus.S2.statusDesc);
        } else if(isUsedUsername){
          message.setStatus(UsernameStatus.S3.status);
          message.setStatusDesc(UsernameStatus.S3.statusDesc);
        } else {
            message.setStatus(UsernameStatus.S200.status);
            message.setStatusDesc(UsernameStatus.S200.statusDesc);
        }

        play.Logger.info("checkUsername = " + Json.toJson(message));

        return message;
    }


    private VerificFormMessage checkPassword(String password, String retypepassword) {
        VerificFormMessage message = new VerificFormMessage();

        message.setInputName("password");

        String passwordRegex = "^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{4,15}$";

        if("".equals(password) || password == null){
            message.setStatus(PasswordStatus.S1.status);
            message.setStatusDesc(PasswordStatus.S1.statusDesc);
        } else if("".equals(retypepassword) || retypepassword == null){
            message.setStatus(PasswordStatus.S2.status);
            message.setStatusDesc(PasswordStatus.S2.statusDesc);
        } else if(!retypepassword.equals(password)){
            message.setStatus(PasswordStatus.S3.status);
            message.setStatusDesc(PasswordStatus.S3.statusDesc);
        } else if(!password.matches(passwordRegex)){
            message.setStatus(PasswordStatus.S4.status);
            message.setStatusDesc(PasswordStatus.S4.statusDesc);
        } else {
            message.setStatus(PasswordStatus.S200.status);
            message.setStatusDesc(PasswordStatus.S200.statusDesc);
        }

        play.Logger.info("checkPassword = " + Json.toJson(message));

        return message;
    }


    /**
     * 產生註冊sha-256認證字串
     * @param email 使用者信箱
     * @return SHA256 String
     */
    public String genSignupAuthString(String email){

      Format formatter = new SimpleDateFormat("yyyyMMddHHmmss");
      String time = formatter.format(new Date());
      String text = email + time;

      return this.genSHA256String(text);
    }


    /**
     * 傳入主要的需要認證字串,進行Sha256加密
     * @param text
     * @return token
     */
    private String genSHA256String(String text){
      String token = "";
      try{
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] digest = md.digest();
        md.update(text.getBytes("UTF-8")); 
        // %064x 意思是,產生64個字串長的字串
        token = String.format("%064x", new java.math.BigInteger(1, digest));
      } catch (Exception e) {
        e.printStackTrace();
      }
      return token;
    }


    /**
     * 產生會員記錄檔
     * @param memberNo 
     * @param device 
     * @param ipAddress 
     * @param status 
     * @return Map<String , String>
     */
    public Map<String , String> genMemberLoginData(String memberNo , String device , String ipAddress , String status){
      Map<String , String> memberLoginLog = new HashMap<String , String>();
      memberLoginLog.put("memberNo", memberNo);
      memberLoginLog.put("device",device);
      memberLoginLog.put("ipAddress",ipAddress);
      memberLoginLog.put("status",status);
      return memberLoginLog;
    }
}


進入檢查使用者輸入的資料時,會先呼叫webService.checkMemberByEmailwebService.checkMemberByUsername,檢查是否有重複資料在資料庫裡。

接下來才去呼叫Utils_Signup.checkSingupRequest的方法。帶入以下三個參數。
SignupRequest : 使用者註冊的資料。
isRegEmail : 是否是註冊過的信箱。(webService.checkMemberByEmail的查詢結果)
isUsedUsername : 是否是註冊過的使用者名稱。(webService.checkMemberByUsername的查詢結果)

public Map<String , VerificFormMessage>checkSingupRequest(SignupRequest reqeuest , boolean isRegEmail , boolean isUsedUsername){
...
}

進入檢查後,會去檢查使用者輸入的資料,回傳個欄位檢驗的結果,來告知使用者填寫的資料,是否能順利註冊。而每個欄位都有自己的狀態,也新增三個Status類別,來儲存檢驗訊息。
分別是EmailStatusPasswordStatusUsernameStatus。每當都會經過自己檢查時,最後會把檢驗結果,放入到VerificFormMessage裡。若欄位狀態不符合200時,回到WebController時,會把錯誤訊息放入到flash裡,回到原本的註冊頁面,提示使用者。

WebController.java
path : /app/controllers/WebController.java

  ...
    Map<String, VerificFormMessage> verificInfo = this.checkSingupRequest(request);
    for (String key : verificInfo.keySet()) {
      // 發現驗證沒過,放入錯誤訊息
      if (!"200".equals(verificInfo.get(key).getStatus())) {
        flash().put(key, verificInfo.get(key).getStatusDesc());
      }
    }
    if(!flash().isEmpty()){
      return ok(signup.render());
    }
    ...


負責儲存驗證欄位的類別。
path : app/pojo/web/signup/verific/VerificFormMessage.java

package pojo.web.signup.verific;

/**
 * 預設表單錯誤訊息
 */
public class VerificFormMessage {

  // 錯誤的欄位名稱
  private String inputName;

  // 狀態碼
  private String status;

  // 狀態碼描述
  private String statusDesc;

  public String getInputName() {
    return inputName;
  }

  public void setInputName(String inputName) {
    this.inputName = inputName;
  }

  public String getStatus() {
    return status;
  }

  public void setStatus(String status) {
    this.status = status;
  }

  public String getStatusDesc() {
    return statusDesc;
  }

  public void setStatusDesc(String statusDesc) {
    this.statusDesc = statusDesc;
  }
}


信箱檢查狀態。
path : app/pojo/web/signup/status/EmailStatus.java

package pojo.web.signup.status;

public enum EmailStatus {


  S1("1", "請輸入信箱。"), 
  S2("2", "信箱格式錯誤。"), 
  S3("3", "該信箱已經註冊過,請更換信箱。"), 
  S200("200", "該信箱可以使用。");


  EmailStatus(String status, String statusDesc) {
    this.status = status;
    this.statusDesc = statusDesc;
  }

  public final String status; // 狀態代碼

  public final String statusDesc; // 狀態說明

}


密碼檢查狀態。
path : app/pojo/web/signup/status/PasswordStatus.java

package pojo.web.signup.status;

public enum PasswordStatus {


  S1("1", "請輸入密碼。"), 
  S2("2", "請輸入確認密碼。"), 
  S3("3", "密碼兩次輸入不相符。"), 
  S4("4", "密碼需要在長度4~15個字元之間,且含有大小寫英文字與數字。"), 
  S200("200", "密碼可以使用。");


  PasswordStatus(String status, String statusDesc) {
    this.status = status;
    this.statusDesc = statusDesc;
  }


  public final String status; // 狀態代碼

  public final String statusDesc; // 狀態說明

}


使用者檢查狀態。
path : app/pojo/web/signup/status/UsernameStatus.java

package pojo.web.signup.status;

public enum UsernameStatus {


  S1("1", "請輸入使用者名稱。"),
  S2("2", "使用者名稱只能使用英文字,且介於4個字~15字之間。"), 
  S3("3", "該使用者名稱已被使用,請改用其它名稱。"), 
  S200("200", "使用者名稱可以使用。");


  UsernameStatus(String status, String statusDesc) {
    this.status = status;
    this.statusDesc = statusDesc;
  }


  public final String status; // 狀態代碼

  public final String statusDesc; // 狀態說明

}


WebService
path : /app/services/WebService.java

/** 檢查是否有該會員存在 */
public boolean checkMemberByEmail(String email);

/** 檢查是否有該會員使用者名稱是否存在 */
public boolean checkMemberByUsername(String username);


查詢的SQL語法。
path :/conf/services/WebService.xml

<!-- 檢查是否有重覆註冊的會員 -->
<select id="checkMemberByEmail" parameterType="String"
    resultType="boolean">
    SELECT count(1) FROM member_main WHERE email=#{email}
</select>


<!-- 檢查是否有重覆的使用者名稱 -->
<select id="checkMemberByUsername" parameterType="String"
    resultType="boolean">
    SELECT count(1) FROM member_main WHERE username=#{username}
</select>

檢查畫面。


Step 3 : 檢核通過,新增會員資料,且尚未認證。
通過檢查之後,代表使用者輸入的註冊資訊,在我們的資料庫,是沒有申請過的。這樣我們就可以新增一筆會員資料進去,且該新申請的會員狀態,是尚未認證的狀態。
特別要注意的地方就是,當有進行資料庫操作的動作時,都需要進行try catch的動作,以免跟資料庫互動時發生錯誤,導致頁面停擺無回應。
path : /app/controllers/WebController.java


    ...

    try {
      // Step 3
      int isSignupOk = webService.signupNewMember(request);
      if(isSignupOk == 0){
        flash().put("signupError", "註冊會員失敗,請重新註冊,謝謝。");
        return ok(signup.render());
      }

      ...

    } catch (Exception e) {
      e.printStackTrace();
      flash().put("signupError", "註冊會員失敗,請重新註冊,謝謝。");
      return ok(signup.render());
    }


呼叫WebService.signupNewMember新增會員資料。在MyBatis新增資料放入的資料是物件,我們需要在interface新增@Param的註記,並可以給予一個別名,在XMLSQL裡,就可以利用這個別名,取出每個欄位的資訊,寫入資料庫的member_main表單。
path : /app/services/WebService.java

public interface WebService {

  /** 註冊新會員 */
  public int signupNewMember(@Param("signupRequest") SignupRequest signupRequest);
  ...
}


新增資料的SQL語法。
email : 信箱
password : 密碼
username : 使用者名稱
DATE_FORMAT(NOW(),'%Y%m%d%H%i%s') => MySQL時間格式轉換,ex:20160924043200
path : /conf/services/WebService.xml

    <!-- 註冊會員 -->
    <insert id="signupNewMember" parameterType="pojo.web.signup.request.SignupRequest">
        insert into
        member_main (memberNo ,email , password , username , createDate, modifyDate)
        values
        ( 
            null , 
            #{signupRequest.email} ,
            #{signupRequest.password},
            #{signupRequest.username} ,
            DATE_FORMAT(NOW() , '%Y%m%d%H%i%s') ,
            DATE_FORMAT(NOW(),'%Y%m%d%H%i%s')
        );
    </insert>


註冊失敗。

新增成功。


Step 4 : 註冊新增成功,新增認證連結資料。
會員註冊成功後,我們會先查詢出會員資料的信箱,組出認證信需要的認證字串,再寫入到member_token表單裡。

path : /app/controllers/WebController.java

    ...
    try {

      ...

      // Step 4
      Utils_Signup utils_Signup = new Utils_Signup();
      Member newMember = webService.findMemberByEmail(request.getEmail());
      String signupAuthString = utils_Signup.genSignupAuthString(newMember.getEmail());

      Map<String , String> memberToken = new HashMap<String , String>();
      memberToken.put("memberNo", newMember.getMemberNo());
      memberToken.put("tokenString", signupAuthString);
      memberToken.put("type", MemberTokenType.Signup.toString());
      int isSingAuthStringOk = webService.genSignupAuthData(memberToken);

      ...

    }catch (Exception e) {
      e.printStackTrace();
      flash().put("signupError", "註冊會員失敗,請重新註冊,謝謝。");
      return ok(signup.render());
    }


webService.findMemberByEmail,查詢會員資料,我們會需要新增一個Member類別,儲存查詢的會員資料。
path : /app/pojo/web/Member.java

package pojo.web;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class Member {

  private String memberNo;

  private String email;

  private String status;

  private String password;

  private String username;

  private String createDate;

  private String modifyDate;

  public String getMemberNo() {
    return memberNo;
  }

  public void setMemberNo(String memberNo) {
    this.memberNo = memberNo;
  }

  public String getEmail() {
    return email;
  }

  public void setEmail(String email) {
    this.email = email;
  }

  public String getStatus() {
    return status;
  }

  public void setStatus(String status) {
    this.status = status;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public String getCreateDate() {
    return createDate;
  }

  public void setCreateDate(String createDate) {
    this.createDate = createDate;
  }

  public String getModifyDate() {
    return modifyDate;
  }

  public void setModifyDate(String modifyDate) {
    this.modifyDate = modifyDate;
  }
}


認證字串使用會員的"電子信箱"與"目前時間"字串,串聯後,使用Sha256加密後產生。

path : /app/utils/signup/Utils_Signup.java

WebController.java
  String authString = utils_Signup.genAuthString(newMember.getEmail());
-----
Utils_Signup.java
/**
 * 產生註冊sha-256認證字串
 * @param email 使用者信箱
 * @return SHA256 String
 */
public String genSignupAuthString(String email){

  Format formatter = new SimpleDateFormat("yyyyMMddHHmmss");
  String time = formatter.format(new Date());
  String text = email + time;

  return this.genSHA256String(text);
}


/**
 * 傳入主要的需要認證字串,進行Sha256加密
 * @param text
 * @return token
 */
private String genSHA256String(String text){
  String token = "";
  try{
    MessageDigest md = MessageDigest.getInstance("SHA-256");
    md.update(text.getBytes("UTF-8")); 
    byte[] digest = md.digest();
    // %064x 意思是,產生64個字串長的字串
    token = String.format("%064x", new java.math.BigInteger(1, digest));
  } catch (Exception e) {
    e.printStackTrace();
  }
  return token;
}
...


member_token狀態表。
會員Token各種狀態表,預留忘記密碼與更換信箱。

package pojo.web;

/** 認證Token 類型*/
public enum MemberTokenType {
  // 註冊
  Signup ,

  // 忘記密碼
  ForgotPassword ,

  // 更換信箱
  ChangeEmail;
}


產生完字串後,新增一筆資料到member_token認證資料。

WebController.java
    int isSingAuthStringOk = webService.genSignupAuthData(memberAuth);
----
WebService.java
  /** 產生會員認證資料  */
  public int genTokenData(@Param("memberToken") Map<String , String> memberToken);

  /** 用Email 尋找會員資料 */
  public Member findMemberByEmail(String email);


新增資料的SQL語法。
authString : 認證字串
memberNo : 會員編號
type : 類型
sendDate : 寄送日期
isUse : 是否使用過
createDate : 建立日期
modifyDate : 修改日期
expiryDate : 過期日期
DATE_FORMAT(NOW(),'%Y%m%d%H%i%s') => MySQL時間格式轉換,ex:20160924043200
path : /conf/services/WebService.xml

    <!-- 註冊認證連結 -->
<insert id="genSignupAuthData" parameterType="Map">
    insert into
    member_token (tokenString, type ,memberNo , sendDate , isUse , createDate , modifyDate , expiryDate)
    values
    ( 
        #{memberToken.tokenString} , 
        #{memberToken.memberNo} ,
        #{memberToken.type} ,
        DATE_FORMAT(NOW() , '%Y%m%d%H%i%s'),
        0,
        DATE_FORMAT(NOW() , '%Y%m%d%H%i%s') ,
        DATE_FORMAT(NOW(),'%Y%m%d%H%i%s') ,
        DATE_FORMAT(DATE_ADD(NOW(),INTERVAL 1 DAY),'%Y%m%d%H%i%s')
    );
</insert>


查詢的SQL語法。

<!-- 根據email查詢會員資料 -->
<select id="findMemberByEmail" parameterType="String"
    resultType="pojo.web.Member">
    SELECT * FROM member_main WHERE email = #{email}
</select>


寄送信件失敗。


新增成功。


Step 5 : 新增會員記錄檔。
會員加入成功後,我們需要新增一筆紀錄資料到member_login_log,紀錄使用者成功加入會員訊息。 path : /app/controllers/WebController.java

    ...
    try {

      ...

      // Step 5
      Map<String , String> memberLoginData 
          = utils_Signup.genMemberLoginData(newMember.getMemberNo() ,
                                            "PC" , 
                                            request().remoteAddress() , 
                                            MemberLoginStatus.S1.getStatus());
      int isMemberLoginLogOk = webService.genMemberLoginLog(memberLoginData);

      ...

    }catch (Exception e) {
      e.printStackTrace();
      flash().put("signupError", "註冊會員失敗,請重新註冊,謝謝。");
      return ok(signup.render());
    }


會員紀錄檔,轉換成Map格式物件。
path : /app/utils/signup/Utils_Signup.java

WebController.java
  Map<String , String> memberLoginData 
      = utils_Signup.genMemberLoginData(newMember.getMemberNo() ,
                                        "PC" , 
                                        request().remoteAddress() , 
                                        MemberLoginStatus.S1.getStatus());
---
Utils_Signup.java
  /**
   * 產生會員記錄檔
   * @param memberNo 
   * @param device 
   * @param ipAddress 
   * @param status 
   * @return Map<String , String>
   */
  public Map<String , String> genMemberLoginData(String memberNo , String device , String ipAddress , String status){
    Map<String , String> memberLoginLog = new HashMap<String , String>();
    memberLoginLog.put("memberNo", memberNo);
    memberLoginLog.put("device",device);
    memberLoginLog.put("ipAddress",ipAddress);
    memberLoginLog.put("status",status);
    return memberLoginLog;
  }


產生完Map物件後,新增一筆資料到member_login_log會員紀錄檔。

WebController.java
  int isMemberLoginLogOk = webService.genMemberLoginLog(memberLoginData);
----
WebService.java
  /** 會員記錄檔  */
  public int genMemberLoginLog(@Param("memberLoginData") Map<String , String> memberLoginData);


新增資料的SQL語法。
memberNo : 會員編號
status : 會員狀態
ipAddress : 網路位址
device : 登入裝置
loginDate : 登入日期
DATE_FORMAT(NOW(),'%Y%m%d%H%i%s') => MySQL時間格式轉換,ex:20160924043200 path : /conf/services/WebService.xml

<!-- 會員記錄檔 -->
<insert id="genMemberLoginLog" parameterType="Map">
    insert into
    member_login_log (memberNo , status , ipAddress , device , loginDate)
    values
    ( 
        #{memberLoginData.memberNo} , 
        #{memberLoginData.status} ,
        #{memberLoginData.ipAddress} ,
        #{memberLoginData.device} ,
        DATE_FORMAT(NOW(),'%Y%m%d%H%i%s')
    );
</insert>


寄送信件失敗。


新增成功。


Step 6 : 進行寄送認證信動作。
最後進行寄送認證信動作。而寄信服務,需要有SMTP Server來寄送電子郵件,我採用Gmail的免費信箱,而GmailSMTP轉寄的服務,來協助我們寄送註冊信件。
path : /app/controllers/WebController.java

  ...
  try {

    ...

    // Step 6
    Utils_Email utils_Email = new Utils_Email();
    Email email = utils_Email.genSinupAuthEmail(newMember, authString);
    boolean isSeadMailOk = utils_Email.sendMail(email);

    ...

  }catch (Exception e) {
    e.printStackTrace();
    flash().put("signupError", "註冊會員失敗,請重新註冊,謝謝。");
    return ok(signup.render());
  }


Email格式大多是固定的,為了傳遞物件方便,新增Email類別,來設定一些Email常見的屬性。
path : /app/pojo/web/email/Email.java

package pojo.web.email;

public class Email {
  // 寄件者
  private String From;

  // 收件者
  private String to;

  // 信件主題
  private String subject;

  // 文字訊息
  private String text;

  // 網頁訊息
  private String content;

  public String getFrom() {
    return From;
  }

  public void setFrom(String from) {
    From = from;
  }

  public String getTo() {
    return to;
  }

  public void setTo(String to) {
    this.to = to;
  }

  public String getSubject() {
    return subject;
  }

  public void setSubject(String subject) {
    this.subject = subject;
  }

  public String getText() {
    return text;
  }

  public void setText(String text) {
    this.text = text;
  }

  public String getContent() {
    return content;
  }

  public void setContent(String content) {
    this.content = content;
  }

}


新增Email寄送程式。
path : /app/utils/mail/Utils_Email.java

package utils.mail;

import java.util.Properties;

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

import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;

import play.libs.Json;
import pojo.web.Member;
import pojo.web.email.Email;
import services.Impl.WebServiceImpl;
import utils.signup.Utils_Signup;

public class Utils_Email {

  public Email genSinupAuthEmail(Member member , String authString){
    Email email = new Email();
    email.setFrom("[email protected]");
    email.setTo(member.getEmail());
    email.setSubject("[STAR] - 註冊認證信");
    email.setText("");
    email.setContent("<h2>您好 "+ member.getUsername()+",你已經成功註冊,請在24小時內,點選以下驗證連結後,便會啟用帳號,謝謝!!</h2> "
                      + "<a href='" +"http://127.0.0.1:9000/web/authMember?auth="+authString+"'>認證連結</a>");
    play.Logger.info("auth email = " + Json.toJson(email));
    return email;
  }

  // 寄信
  public boolean sendMail(Email email) {

    Session session  = null;

    try {
      // 取得SMTP設定檔
      Properties props = this.getMailSMTPConf();

      // 進行授權認證動作
      session  = Session.getInstance(props, new javax.mail.Authenticator() {
        protected PasswordAuthentication getPasswordAuthentication() {
          return new PasswordAuthentication(props.getProperty("user"), props.getProperty("password"));                                                      
        }
      });

      MimeMessage message = new MimeMessage(session);

      String to = email.getTo();
      message.setFrom(new InternetAddress(email.getFrom()));
      message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
      message.setSubject(email.getSubject());
      if(!"".equals(email.getText())){
        message.setText(email.getText());
      }
      if(!"".equals(email.getContent())){
        message.setContent(email.getContent(), "text/html; charset=utf-8");
      }

      // 寄送訊息
      Transport.send(message);
      play.Logger.info("信件寄送成功");
      return true;
    } catch (MessagingException e) {
      play.Logger.error("信件寄送失敗");
      e.printStackTrace();
      return false;
    } finally {
      // 成功寄信完,清除Session
      session = null;
    }
  }


  // 取得conf/application.conf設定檔
  public Properties getMailSMTPConf(){
    ClassLoader classLoader = utils.mail.Utils_Email.class.getClassLoader();
    Config config = ConfigFactory.load(classLoader);

    String host         = config.getString("mail.smtp.host");
    int port            = config.getInt("mail.smtp.port");
    final String user   = config.getString("mail.smtp.user");
    String password     = config.getString("mail.smtp.password");
    final boolean auth  = config.getBoolean("mail.smtp.auth");
    String ssl_trust    = config.getString("mail.smtp.ssl.trust");
    String socketFactory_class = config.getString("mail.smtp.socketFactory.class");

    play.Logger.info("host        = " + host);
    play.Logger.info("port        = " + port);
    play.Logger.info("user        = " + user);
    play.Logger.info("password    = " + password);
    play.Logger.info("auth        = " + auth);
    play.Logger.info("ssl_trust   = " + ssl_trust);
    play.Logger.info("socketFactory_class    = " + socketFactory_class);

    Properties props = new Properties();
    props.put("mail.smtp.host", host);
    props.put("mail.smtp.port", port);
    props.put("mail.smtp.socketFactory.port", port);
    props.put("mail.smtp.socketFactory.class", socketFactory_class);
    props.put("mail.smtp.auth", auth);
    props.put("mail.smtp.ssl.trust", ssl_trust);
    props.put("user", user);
    props.put("password", password);

    return props;
  }
}


調整application.conf,新增SMTP相關設定。
path : conf/application.conf

...

# 設定我們的 mail smtp寄信服務
mail.smtp{
    host="smtp.gmail.com"
    port=465
    ssl.trust="smtp.gmail.com"
    socketFactory.class="javax.net.ssl.SSLSocketFactory"
    auth="true"
    user="[email protected]"  <--這邊請更換成,您自己的Gmail信箱
    password="xxx"        <--這邊請更換成,您自己的Gmail信箱密碼
}
...


實際寄送註冊信件流程
1.genSinupAuthEmail : 傳入Member會員資料,轉換成Email物件,依序設定主旨、收件者、信件主旨、信件內文。
2.sendMail : 傳入要寄信的Email物件,會依序先取得application.confSMTP設定值後,會跟Google進行帳號驗證後,再透過GmailSMTP,把我們的註冊信件送出。

WebController.java
    Utils_Email utils_Email = new Utils_Email();
    Email email = utils_Email.genSinupAuthEmail(newMember, authString);
    boolean isSeadMailOk = utils_Email.sendMail(email);
---
Utils_Email.java
  public Email genSinupAuthEmail(Member member , String authString) ...
-
  public boolean sendMail(Email email) ...


Note : Google本身採用比較高的權限控管,若要使用SMTP的服務,先必須登入您的Google帳號,調整使用者的安全性調整為開啟,我們的寄信程式才能順利寄送信件。
網址 : https://www.google.com/settings/security/lesssecureapps

寄送失敗。

寄送成功。


Step 7 : 以上都順利完成,導入成功註冊頁面。


[Final]

這個小節,實際上花了快3個星期才完成,這期間調整前端的網頁畫面,以及後端的註冊資料的驗證與測試、寄送信件的測試。每個小節都幾乎是前面所有章節學到的技術統合,這邊相對需要花較多時間,才有辦法順利上手,往後幾個章節,才會相對輕鬆許多。若您完成之後,再前往下個小節,這部份講解會員認證信部份。

若有餘力,可以嘗試不同的註冊方式,可以練習,先用信箱申請註冊後,先傳送註冊連結,再往下申請帳號,這樣的優點是,若是經過這個步驟申請的帳號資料,會確保會員的電子信箱可以寄送到的,減少輸入錯誤信箱,而註冊失敗的案例。


[Reference]
Java Mail 使用GMail (SMTP) Server
http://pclevin.blogspot.tw/2014/11/java-mail-gmail-smtp-server.html

hash-string-via-sha-256-in-java
http://stackoverflow.com/questions/3103652/hash-string-via-sha-256-in-java

results matching ""

    No results matching ""