Ch13-2 : Edit Profile 1
[Profile 1]
這個小節我們要設計會員明細資料,回顧我們的member_main這個表單設計,還有使用者名稱沒有開放修改,也會納入修改會員資料的範圍。
那我們這次會員資料的編輯,是為了補足註冊時,沒有填寫的詳細資料,參考目前主流的社群平台之後,我這次會新增一個會員明細資料表與資料log表。這個資料表是member_detail,那這個資料表基本欄位會有頭像的連結、生日、手機、暱稱這四個欄位,資料表的primary key會是member_main的memberNo。
不過各位可能會有疑問,為什麼不開放儲存照片檔案,這樣我們資料表設計就得考慮到額外空間去儲存使用者的頭像,為了減少複雜,我這次只給新增頭像連結即可。
那以下開始說明如何完成這個小節。
[設計階段]
這次我們這個小節,會採用網頁目前流行的ajax(非同步的JavaScript與XML技術)方式讓使用者去編輯會員資料,依照我們想要的修改資料部分有member_main的username與要新增的member_detail的nickname、birthday、cellphone、headerPicLink這四個欄位,總計會要修改五個欄位需要填寫。
因為這次採用ajax去即時檢核使用者輸入這些欄位時,需要的5個ajax檢核使用者的輸入是否正確,除了useranme(使用者名稱)必須填寫之外,剩下四個欄位檢核,使用者可以填寫或不填寫都可以,最後一樣使用ajax進行所有資料驗證,確認可以之後,才允許使用者去做新修改或新增的動作。
因為這次頁面採用全ajax處理頁面上的會員編輯,那我們的驗證欄位的回傳格式會一律是Json格式,裡面會有一些狀態代碼,狀態說明,以及該欄位是否檢核成功,目的是為了一致性,這樣我們在撰寫相關JS時會比較統一,那以下開始介紹相關表單與相關程式。
[相關表單] member_detail
會員明細資料,當使用者使用編輯會員資料時,會寫入或更新以下資訊。
-- 會員明細
-- member_detail
-- memberNo 會員編號
-- headerPicLink 頭像網址
-- birthday 生日
-- nickname 暱稱
-- cellphone 手機號碼
-- createDate 建立日期
-- modifyDate 修改日期
CREATE TABLE `member_detail` (
`memberNo` VARCHAR(15) NOT NULL NULL COLLATE 'utf8_unicode_ci',
`nickname` VARCHAR(50) NULL NULL COLLATE 'utf8_unicode_ci',
`birthday` VARCHAR(50) NULL NULL COLLATE 'utf8_unicode_ci',
`cellphone` VARCHAR(50) NULL NULL COLLATE 'utf8_unicode_ci',
`headerPicLink` VARCHAR(500) NULL NULL COLLATE 'utf8_unicode_ci',
`createDate` VARCHAR(50) NOT NULL NULL COLLATE 'utf8_unicode_ci',
`modifyDate` VARCHAR(50) NOT NULL NULL COLLATE 'utf8_unicode_ci',
CONSTRAINT pk_memberNo PRIMARY KEY (memberNo),
INDEX (cellphone)
)COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
;
member_detail_log
會員明細修改紀錄表,這個表的設計是為了紀錄使用者的修改紀錄
-- 會員明細修改紀錄表
-- member_detail
-- memberNo 會員編號
-- headerPicLink 頭像網址
-- birthday 生日
-- nickname 暱稱
-- cellphone 手機號碼
-- createDate 建立日期
CREATE TABLE `member_detail_log` (
`memberNo` VARCHAR(15) NOT NULL NULL COLLATE 'utf8_unicode_ci',
`nickname` VARCHAR(50) NULL NULL COLLATE 'utf8_unicode_ci',
`birthday` VARCHAR(50) NULL NULL COLLATE 'utf8_unicode_ci',
`cellphone` VARCHAR(50) NULL NULL COLLATE 'utf8_unicode_ci',
`headerPicLink` VARCHAR(500) NULL NULL COLLATE 'utf8_unicode_ci',
`createDate` VARCHAR(50) NOT NULL NULL COLLATE 'utf8_unicode_ci',
index (memberNo)
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
;
[相關程式路徑]
app
└ controllers
└ WebController.java <-- 新增Edit Profile相關功能
└ pojo
└ web
└ MemberDetail.java <--- 新增會員明細資料
MemberProfile.java <--- 頁面會員編輯資料
└ signup
└ status
└ BirthDayStatus.java <--- 生日檢核狀態
CellphoneStatus.java <--- 手機檢核狀態
HeaderPicLinkStatus.java <--- 頭像檢核狀態
NicknameStatus.java <--- 暱稱檢核狀態
UpdateMemberProfileStatus.java <--- 更新會員資料狀態
UsernameStatus.java <--- 調整使用者名稱檢核狀態
└ update.java
└ UpdateMessage.java <--- 更新結果訊息
UpdateType.java <--- 更新類型種類
└ verific
└ VerificCheckMessage.java <--- 檢查欄位資訊是否正確
└ services
└ Impl
└ WebServiceImpl.java <--- 實作WebService的新功能
└ WebService.java <--- 新增會員明細相關DB服務
└ utils
└ enc
└ EncAndDeCodeTool.java <--- 加解密工具
└ http
└ HttpHelper.java <--- Http工具
└ signup
└ Utils_Signup.java <--- 新增會員明細相關功能
└ views
└ web
└ headerJqueryuiLibs.scala.html <--- 新增Jqeury 日期選擇器相關libary
headerNav.scala.html <--- 調整新增會員編輯連結
└ loginSignup
└ editProfile.scala.html <--- 編輯會員資料頁面
conf
└ routes <--- 新增相關會員編輯服務
└ services
└ WebService.xml <--- 新增會員編輯相關SQL
public
└ sql
└ profile.sql <--- 會員編輯相關Table資料
└ javascripts
└ profileUtils.js <--- 會員編輯主要js
webUtils.js <--- 公用Web主要js
└ jquery-ui-1.12.1
└ LICENSE.txt <--- jqeury ui說明
datepicker-zh-TW.js <--- jqeury Ui繁體中文js
jquery-1.12.4.js <--- jquery ui需要的js
jquery-ui.css <--- jquery ui主要樣式表
jquery-ui.js <--- jquery ui主要js
package.json <--- jquery ui主要的檔案相依性
images
└ calendar.gif <--- 日期選擇器的icon圖
ui-icons_444444_256x240.png <--- 日期選擇器相關圖
ui-icons_555555_256x240.png
ui-icons_777620_256x240.png
ui-icons_777777_256x240.png
ui-icons_cc0000_256x240.png
ui-icons_ffffff_256x240.png
[後端相關程式]
在寫這個章節時,實際上,我是依序把每個欄位的功能,逐一實作出來,邊寫邊調整,逐漸規劃出比較合適的寫法,這個小節會介紹後端相關功能,下一個小節介紹前端頁面需要的頁面與JS,最後一個小節是進行測試與驗證,來完成編輯會員明細的功能。
app.pojo.web.MemberDetail.java
使用者的會員明細資料,裡面會有頭像、生日、手機、暱稱這四個欄位,主要用途是寫入資料庫,或讀取明細所使用。
package pojo.web;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class MemberDetail {
private String memberNo;
private String nickname;
private String birthday;
private String cellphone;
private String headerPicLink;
private String createDate;
private String modifyDate;
public String getMemberNo() {
return memberNo;
}
public void setMemberNo(String memberNo) {
this.memberNo = memberNo;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
public String getCellphone() {
return cellphone;
}
public void setCellphone(String cellphone) {
this.cellphone = cellphone;
}
public String getHeaderPicLink() {
return headerPicLink;
}
public void setHeaderPicLink(String headerPicLink) {
this.headerPicLink = headerPicLink;
}
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;
}
}
app.pojo.web.MemberProfile.java
這個類別設計,是因為混合了Member與MemberDetail的欄位資訊,設計出單純給會員編輯頁面使用。
package pojo.web;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class MemberProfile {
private String username;
private String nickname;
private String birthday;
private String cellphone;
private String headerPicLink;
private boolean editable;
private String systemMessage;
public MemberProfile(){
this.username = "";
this.nickname = "";
this.birthday = "";
this.cellphone = "";
this.headerPicLink = "";
this.editable = false;
this.systemMessage = "系統異常,請稍候再嘗試。";
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
public String getCellphone() {
return cellphone;
}
public void setCellphone(String cellphone) {
this.cellphone = cellphone;
}
public String getHeaderPicLink() {
return headerPicLink;
}
public void setHeaderPicLink(String headerPicLink) {
this.headerPicLink = headerPicLink;
}
public boolean isEditable() {
return editable;
}
public void setEditable(boolean editable) {
this.editable = editable;
}
public String getSystemMessage() {
return systemMessage;
}
public void setSystemMessage(String systemMessage) {
this.systemMessage = systemMessage;
}
}
app.pojo.web.signup.status.BirthDayStatus.java
這個類別是針對會員明細的生日作檢核的資訊,裡面描述填寫生日時,相關檢核結果。
package pojo.web.signup.status;
public enum BirthDayStatus {
/** 生日格式不正確,請重新選擇。 */
S1("1", "生日格式不正確,請重新選擇。" , false),
/** 生日超過現在時間,請重新選擇。 */
S2("2", "生日超過現在時間,請重新選擇。" , false),
/** 生日正確。*/
S200("200", "生日正確。" , true) ,
/** 生日無輸入,不需要顯示說明*/
S201("201", "" , true) ,
/** 生日與DB生日一致,不需要顯示說明*/
S202("202", "" , true)
;
BirthDayStatus(String status, String statusDesc , boolean pass) {
this.status = status;
this.statusDesc = statusDesc;
this.pass = pass;
}
/** 狀態代碼 */
public final String status;
/** 狀態說明 */
public final String statusDesc;
/** 欄位驗證是否可以通過 */
public final boolean pass;
}
app.pojo.web.signup.status.CellphoneStatus.java
這個類別是針對會員明細的手機作檢核的資訊,裡面描述填寫手機時,相關檢核結果。
package pojo.web.signup.status;
public enum CellphoneStatus {
S1("1", "手機號碼格式不正確,請重新輸入。" , false),
S2("2", "該手機號碼已在使用中,請重新輸入。" , false),
S200("200", "手機號碼可以使用。" , true) ,
/** 手機號碼無輸入,不需要顯示說明 */
S201("201", "" , true) ,
/** 手機號碼為同一人,不需要顯示說明 */
S202("202", "" , true)
;
CellphoneStatus(String status, String statusDesc , boolean pass) {
this.status = status;
this.statusDesc = statusDesc;
this.pass = pass;
}
/** 狀態代碼 */
public final String status;
/** 狀態說明 */
public final String statusDesc;
/** 欄位驗證是否可以通過 */
public final boolean pass;
}
app.pojo.web.signup.status.HeaderPicLinkStatus.java
這個類別是針對會員明細的頭像作檢核的資訊,裡面描述填寫頭像時,相關檢核結果。
package pojo.web.signup.status;
public enum HeaderPicLinkStatus {
/** 圖片網址不正確,請重新輸入。 */
S1("1", "圖片網址不正確,請重新輸入。" , false),
/** 圖片網址無法載入圖片,請重新輸入。 */
S2("2", "圖片網址無法載入圖片,請重新輸入。" , false),
/** 圖片網址可以使用。*/
S200("200", "圖片網址可以使用。" , true) ,
/** 圖片網址無輸入,不需要顯示說明*/
S201("201", "" , true) ,
/** 圖片網址相同,不需要顯示說明*/
S202("202", "" , true)
;
HeaderPicLinkStatus(String status, String statusDesc , boolean pass) {
this.status = status;
this.statusDesc = statusDesc;
this.pass = pass;
}
/** 狀態代碼 */
public final String status;
/** 狀態說明 */
public final String statusDesc;
/** 欄位驗證是否可以通過 */
public final boolean pass;
}
app.pojo.web.signup.status.NicknameStatus.java
這個類別是針對會員明細的暱稱作檢核的資訊,裡面描述填寫暱稱時,相關檢核結果。
package pojo.web.signup.status;
public enum NicknameStatus {
/** 暱稱長度只能且介於1個字~15字之間。 */
S1("1", "暱稱長度只能且介於1個字~15字之間。" , false),
/** "暱稱不能含有特殊字元,請您重新輸入。 */
S2("2", "暱稱不能含有特殊字元,請您重新輸入。" , false),
/** 暱稱可以使用。 */
S200("200", "暱稱可以使用。" , true) ,
/** 暱稱無輸入,不需要顯示說明 */
S201("201", "" , true) ,
/** 暱稱相同,不需要顯示說明 */
S202("202", "" , true) ,
;
NicknameStatus(String status, String statusDesc , boolean pass) {
this.status = status;
this.statusDesc = statusDesc;
this.pass = pass;
}
/** 狀態代碼 */
public final String status;
/** 狀態說明 */
public final String statusDesc;
/** 欄位驗證是否可以通過 */
public final boolean pass;
}
app.pojo.web.signup.status.UsernameStatus.java
這個類別是針對會員的使用者名稱作檢核的資訊,裡面描述填寫使用者名稱時,相關檢核結果。這個類別之前使用在會員註冊,這次調整成,可以符合修改會員資料的格式。
package pojo.web.signup.status;
public enum UsernameStatus {
/** 請輸入使用者名稱。*/
S1("1", "請輸入使用者名稱。" , false),
/** 使用者名稱只能使用英文字,且介於4個字~15字之間。 */
S2("2", "使用者名稱只能使用英文字,且介於4個字~15字之間。", false),
/** 該使用者名稱已被使用,請改用其它名稱。 */
S3("3", "該使用者名稱已被使用,請改用其它名稱。" , false),
/** 使用者名稱可以使用。 */
S200("200", "使用者名稱可以使用。" , true) ,
/** 使用者名稱與原本相同,不顯示任何文字訊息*/
S201("201", "" , true),
;
UsernameStatus(String status, String statusDesc , boolean pass) {
this.status = status;
this.statusDesc = statusDesc;
this.pass = pass;
}
/** 狀態代碼 */
public final String status;
/** 狀態說明 */
public final String statusDesc;
/** 欄位驗證是否可以通過 */
public final boolean pass;
}
app.pojo.web.signup.status.UpdateMemberProfileStatus.java
這個類別是針對編即會員的整體檢核資訊,裡面描述編輯會員明細時,整體檢核結果。
package pojo.web.signup.status;
public enum UpdateMemberProfileStatus {
/** 系統忙碌中,請稍後再編輯您的會員資料,謝謝。 */
SE1("SE1", "系統忙碌中,請稍後再編輯您的會員資料。" , false),
/** 您的會員資料修改有誤,請檢查以上填寫資訊是否正確,謝謝。 */
E1("E1" , "您的會員資料修改有誤,請檢查以上填寫資訊是否正確,謝謝。" , false),
/** 您的會員資料,更新成功。*/
S200("S200", "會員資料編輯成功。" , true)
;
UpdateMemberProfileStatus(String status, String statusDesc , boolean update) {
this.status = status;
this.statusDesc = statusDesc;
this.update = update;
}
/** 狀態代碼 */
public final String status;
/** 狀態說明 */
public final String statusDesc;
/** 是否更新成功 */
public final boolean update;
}
app.pojo.web.signup.verific.VerificCheckMessage.java
這個類別用途是針對這次編輯會員方式,新增的類別,主要跟VerificFormMessage.java區別是多增加了pass欄位讓前端知道是否檢核成功。
package pojo.web.signup.verific;
/**
* 檢查欄位資訊是否正確
* 並新增boolean值檢視欄位是否通過
*/
public class VerificCheckMessage extends VerificFormMessage {
private boolean pass;
public boolean isPass() {
return pass;
}
public void setPass(boolean pass) {
this.pass = pass;
}
}
app.pojo.web.signup.update.UpdateMessage.java
這個類別設計用途是,使用者填寫所有欄位時,相關的更新訊息與相關欄位檢核的資訊。
package pojo.web.signup.update;
import java.util.Map;
import pojo.web.signup.verific.VerificCheckMessage;
/**
* 更新結果訊息
*/
public class UpdateMessage {
// 更新類型
private String updateType;
// 狀態碼
private String status;
// 狀態碼描述
private String statusDesc;
// 是否更新成功
private boolean update;
// 檢驗結果
private Map<String , VerificCheckMessage> verificResults;
// 耗費時間
private String costTime;
public String getUpdateType() {
return updateType;
}
public void setUpdateType(String updateType) {
this.updateType = updateType;
}
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;
}
public boolean isUpdate() {
return update;
}
public void setUpdate(boolean update) {
this.update = update;
}
public Map<String, VerificCheckMessage> getVerificResults() {
return verificResults;
}
public void setVerificResults(Map<String, VerificCheckMessage> verificResults) {
this.verificResults = verificResults;
}
public String getCostTime() {
return costTime;
}
public void setCostTime(String costTime) {
this.costTime = costTime;
}
}
app.pojo.web.signup.update.UpdateType.java
這個類別用途是給UpdateMessage這個類別所區別,讓使用者知道目前更新或寫入資料是哪個服務。
package pojo.web.signup.update;
/**
* 更新類型種類
*/
public class UpdateType {
/** 更新會員檔案 */
public final static String updateMemberProfile = "updateMemberProfile";
}
app.services.WebService.java
實作編輯會員明細相關服務。
...
/** 檢查使用者手機是否重覆 */
public boolean checkMemberDetailByCellphone(@Param("cellphone") String cellphone);
/** 用手機號碼 尋找會員明細*/
public MemberDetail findMemberDetailByMemberNo(@Param("memberNo") String memberNo);
/** 更新使用者的使用者名稱 */
public int updateMemberUsername(@Param("memberNo") String memberNo, @Param("newUsername") String newUsername);
/** 寫入或更新memberDetail資料 */
public int genMemberDetail(@Param("data") MemberDetail memberDetail);
/** 寫入memberDetailLog資料 */
public int genMemberDetailChangeLog(@Param("data") MemberDetail memberDetail);
...
app.services.Impl.WebServiceImpl.java
實作WebService內的方法。
...
@Override
public boolean checkMemberDetailByCellphone(@Param("cellphone") String cellphone ){
return this.webService.checkMemberDetailByCellphone(cellphone);
}
@Override
public MemberDetail findMemberDetailByMemberNo(@Param("memberNo") String memberNo ){
return this.webService.findMemberDetailByMemberNo(memberNo);
}
@Override
public int updateMemberUsername(@Param("memberNo") String memberNo, @Param("newUsername") String newUsername){
return this.webService.updateMemberUsername(memberNo , newUsername);
}
@Override
public int genMemberDetail(@Param("data") MemberDetail memberDetail){
return this.webService.genMemberDetail(memberDetail);
}
@Override
public int genMemberDetailChangeLog(@Param("data") MemberDetail memberDetail){
return this.webService.genMemberDetailChangeLog(memberDetail);
}
...
conf.services.WebService.xml
編輯會員明細相關寫入與查詢SQL。
<!-- 檢查是否有重覆的手機號碼-->
<select id="checkMemberDetailByCellphone" parameterType="Map" resultType="boolean">
SELECT CASE WHEN EXISTS (SELECT 1 FROM member_detail de WHERE de.cellphone=#{cellphone})
THEN 1 ELSE 0 END isUsedCellphone
FROM DUAL
</select>
<!-- 根據手機號碼查詢會員明細資料 -->
<select id="findMemberDetailByMemberNo" parameterType="String" resultType="pojo.web.MemberDetail">
SELECT * FROM member_detail WHERE memberNo = #{memberNo}
</select>
<!-- 更新會員使用者名稱 -->
<update id="updateMemberUsername">
UPDATE member_main SET username = #{newUsername} , modifyDate = DATE_FORMAT(NOW(), '%Y%m%d%H%i%s') WHERE memberNo = #{memberNo}
</update>
<!-- 新增會員明細或更新 -->
<insert id="genMemberDetail" parameterType="pojo.web.MemberDetail">
insert into
member_detail
(memberNo , nickname , birthday , cellphone , headerPicLink , createDate , modifyDate)
values
(
#{data.memberNo} ,
#{data.nickname} ,
#{data.birthday} ,
#{data.cellphone} ,
#{data.headerPicLink} ,
DATE_FORMAT(NOW(), '%Y%m%d%H%i%s') ,
DATE_FORMAT(NOW(), '%Y%m%d%H%i%s')
) ON DUPLICATE KEY UPDATE
memberNo = memberNo ,
nickname = #{data.nickname} ,
birthday = #{data.birthday} ,
cellphone = #{data.cellphone} ,
headerPicLink = #{data.headerPicLink} ,
createDate = createDate ,
modifyDate = DATE_FORMAT(NOW(), '%Y%m%d%H%i%s')
</insert>
<!-- 會員明細記錄檔 -->
<insert id="genMemberDetailChangeLog" parameterType="pojo.web.MemberDetail">
insert into
member_detail_log(memberNo , nickname , birthday , cellphone , headerPicLink , createDate)
values
(
#{data.memberNo} ,
#{data.nickname} ,
#{data.birthday} ,
#{data.cellphone} ,
#{data.headerPicLink} ,
DATE_FORMAT(NOW(), '%Y%m%d%H%i%s')
)
</insert>
app.utils.enc.EncAndDeCodeTool.java
因應頁面都是使用ajax檢核欄位資訊,所以我們針對Query String傳入時,都進行URL與Base64的加密,到達檢查時才解密成原本的資料進行驗證。
package utils.enc;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Base64;
/**
* <pre>
* 加解密工具
* </pre>
*/
public class EncAndDeCodeTool {
/**
* 傳入字串,會進行URL與Base64加密
*/
public String urlAndBase64Encode(String unencodeStr){
return this.base64Encode(this.urlEncode(unencodeStr));
}
/**
* 傳入加密過Base64與URL的字串,進行解密
*/
public String urlAndBase64Decode(String encodeStr){
return this.urlDecode(this.base64Decode(encodeStr));
}
public String urlEncode(String encodeStr){
try {
return URLEncoder.encode(encodeStr , "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "";
}
public String base64Encode(String encodeStr){
return new String (Base64.getEncoder().encode(encodeStr.getBytes()));
}
public String urlDecode(String decodeStr){
try {
return URLDecoder.decode(decodeStr , "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "";
}
public String base64Decode(String decodeStr){
return new String (Base64.getDecoder().decode(decodeStr.getBytes()));
}
}
app.utils.http.HttpHelper.java
我們的圖片檢核,因為全都改用後端檢查,我們需要寫一個服務去檢視使用者填寫的頭像網址,確認它是圖片。
package utils.http;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import play.libs.ws.*;
/**
* <pre>
* Http 工具
* </pre>
*/
public class HttpHelper {
private WSClient ws ;
public HttpHelper(WSClient ws){
this.ws = ws;
}
/**
* <pre>
* 檢查是否是圖片
* </pre>
*/
public boolean checkImgUrl(String url) {
WSResponse response = null;
int responseStatus = 0;
boolean isImg = false;
String contentType = "";
try {
String regex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";
// 檢驗是否是網址,才執行檢查
if(url.matches(regex)){
// 設定要請求的資訊
WSRequest request = ws.url(url);
request.setRequestTimeout(10000);
CompletionStage<WSResponse> responsePromise = request.get();
if (responsePromise != null) {
// 取得呼叫完的結果
CompletableFuture<WSResponse> future = responsePromise.toCompletableFuture();
response = future.get();
responseStatus = response.getStatus(); // Http回覆碼
contentType = response.getHeader("Content-Type");
if( contentType == null || "".equals(contentType) ||
contentType.indexOf("image") == -1 || responseStatus != 200){
isImg = false;
} else {
isImg = true;
}
} else {
isImg = false;
}
} else {
isImg = false;
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
play.Logger.info("isImg = " + isImg + " , url = " + url + " , Header Content-Type = " + contentType);
return isImg;
}
}
app.utils.signup.Utils_Signup.java
這個類別主要用途是,使用者編輯會員資料時,需要的欄位檢核與驗證,確認使用者填入的資料是正確的,以及會員相關資料的轉換。
// 產生會員資料
public MemberProfile genMemberProfile(Member member, MemberDetail memberDetail) {
MemberProfile profile = new MemberProfile();
profile.setUsername(member.getUsername()!=null && !"".equals(member.getUsername()) ? member.getUsername() : "");
if(memberDetail != null){
profile.setHeaderPicLink(memberDetail.getHeaderPicLink()!=null && !"".equals(memberDetail.getHeaderPicLink()) ? memberDetail.getHeaderPicLink() : "");
profile.setNickname(memberDetail.getNickname()!=null && !"".equals(memberDetail.getNickname()) ? memberDetail.getNickname() : "");
profile.setCellphone(memberDetail.getCellphone()!=null && !"".equals(memberDetail.getCellphone()) ? memberDetail.getCellphone() : "");
String birthday = "";
if(memberDetail.getBirthday()!=null && !"".equals(memberDetail.getBirthday())){
String year = memberDetail.getBirthday().substring(0, 4);
String month = memberDetail.getBirthday().substring(4, 6);
String day = memberDetail.getBirthday().substring(6, 8);
birthday = year + "/" + month + "/" + day;
}
profile.setBirthday(birthday);
}
profile.setEditable(member.getMemberNo() == null || "".equals(member.getMemberNo()) ? false : true);
profile.setSystemMessage(member.getMemberNo() == null || "".equals(member.getMemberNo()) ? UpdateMemberProfileStatus.SE1.statusDesc : "");
return profile;
}
// 驗證頭像
public VerificCheckMessage checkHeaderPicLink(String headerPicLink , String dbHeaderPicLink , boolean isImg) {
VerificCheckMessage message = new VerificCheckMessage();
message.setInputName("headerPicLink");
String urlRegex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";
if (headerPicLink == null || "".equals(headerPicLink)) {
message.setStatus(HeaderPicLinkStatus.S201.status);
message.setStatusDesc(HeaderPicLinkStatus.S201.statusDesc);
message.setPass(HeaderPicLinkStatus.S201.pass);
} else if (!headerPicLink.matches(urlRegex)) {
message.setStatus(HeaderPicLinkStatus.S1.status);
message.setStatusDesc(HeaderPicLinkStatus.S1.statusDesc);
message.setPass(HeaderPicLinkStatus.S1.pass);
} else if (!isImg) {
message.setStatus(HeaderPicLinkStatus.S2.status);
message.setStatusDesc(HeaderPicLinkStatus.S2.statusDesc);
message.setPass(HeaderPicLinkStatus.S2.pass);
} else if (headerPicLink.equals(dbHeaderPicLink)) {
message.setStatus(HeaderPicLinkStatus.S202.status);
message.setStatusDesc(HeaderPicLinkStatus.S202.statusDesc);
message.setPass(HeaderPicLinkStatus.S202.pass);
} else {
message.setStatus(HeaderPicLinkStatus.S200.status);
message.setStatusDesc(HeaderPicLinkStatus.S200.statusDesc);
message.setPass(HeaderPicLinkStatus.S200.pass);
}
play.Logger.info("checkHeaderPicLink = " + Json.toJson(message));
return message;
}
// 驗證生日
public VerificCheckMessage checkBirthday(String birthday, String dbBirthday) {
VerificCheckMessage message = new VerificCheckMessage();
message.setInputName("birthday");
String birthDayRegex = "(((19|20)([2468][048]|[13579][26]|0[48])|2000)[/-]02[/-]29|((19|20)[0-9]{2}[/-](0[4678]|1[02])"+
"[/-](0[1-9]|[12][0-9]|30)|(19|20)[0-9]{2}[/-](0[1359]|11)[/-](0[1-9]|[12][0-9]|3[01])|(19|20)"+
"[0-9]{2}[/-]02[/-](0[1-9]|1[0-9]|2[0-8])))";
if (birthday == null || "".equals(birthday)) {
message.setStatus(BirthDayStatus.S201.status);
message.setStatusDesc(BirthDayStatus.S201.statusDesc);
message.setPass(BirthDayStatus.S201.pass);
} else if (!birthday.matches(birthDayRegex)) {
message.setStatus(BirthDayStatus.S1.status);
message.setStatusDesc(BirthDayStatus.S1.statusDesc);
message.setPass(BirthDayStatus.S1.pass);
} else if (birthday.replaceAll("/", "").equals(dbBirthday)) {
message.setStatus(BirthDayStatus.S202.status);
message.setStatusDesc(BirthDayStatus.S202.statusDesc);
message.setPass(BirthDayStatus.S202.pass);
} else {
// ex : 2017/03/01 => {2017,03,01}
String[] birthdayArray = birthday.split("/");
Calendar now = Calendar.getInstance();
Calendar memberbirthDay = (Calendar) now.clone();
memberbirthDay.set(Integer.parseInt(birthdayArray[0]),
Integer.parseInt(birthdayArray[1])-1, // 0~11月
Integer.parseInt(birthdayArray[2]));
if(memberbirthDay.getTime().getTime() > now.getTime().getTime()){
message.setStatus(BirthDayStatus.S2.status);
message.setStatusDesc(BirthDayStatus.S2.statusDesc);
message.setPass(BirthDayStatus.S2.pass);
} else {
message.setStatus(BirthDayStatus.S200.status);
message.setStatusDesc(BirthDayStatus.S200.statusDesc);
message.setPass(BirthDayStatus.S200.pass);
}
}
play.Logger.info("checkBirthday = " + Json.toJson(message));
return message;
}
// 驗證使用者名稱
public VerificCheckMessage checkEditUsername(String username, String dbUsername,
boolean isUsedUsername) {
VerificCheckMessage message = new VerificCheckMessage();
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);
message.setPass(UsernameStatus.S1.pass);
} else if (username.equals(dbUsername)) {
message.setStatus(UsernameStatus.S201.status);
message.setStatusDesc(UsernameStatus.S201.statusDesc);
message.setPass(UsernameStatus.S201.pass);
} else if (!username.matches(usernameRegex)) {
message.setStatus(UsernameStatus.S2.status);
message.setStatusDesc(UsernameStatus.S2.statusDesc);
message.setPass(UsernameStatus.S2.pass);
} else if (isUsedUsername) {
message.setStatus(UsernameStatus.S3.status);
message.setStatusDesc(UsernameStatus.S3.statusDesc);
message.setPass(UsernameStatus.S3.pass);
} else {
message.setStatus(UsernameStatus.S200.status);
message.setStatusDesc(UsernameStatus.S200.statusDesc);
message.setPass(UsernameStatus.S200.pass);
}
play.Logger.info("checkEditUsername = " + Json.toJson(message));
return message;
}
// 驗證暱稱
public VerificCheckMessage checkNickname(String nickname , String dbNickname) {
VerificCheckMessage message = new VerificCheckMessage();
message.setInputName("nickname");
String nicknameRegex =
".*[`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】『;:」「』。,、?\\\\]+.*";
if (nickname == null || "".equals(nickname)) {
message.setStatus(NicknameStatus.S201.status);
message.setStatusDesc(NicknameStatus.S201.statusDesc);
message.setPass(NicknameStatus.S201.pass);
} else if (nickname.length() < 1 || nickname.length() > 15) {
message.setStatus(NicknameStatus.S1.status);
message.setStatusDesc(NicknameStatus.S1.statusDesc);
message.setPass(NicknameStatus.S1.pass);
} else if (nickname.matches(nicknameRegex)) {
message.setStatus(NicknameStatus.S2.status);
message.setStatusDesc(NicknameStatus.S2.statusDesc);
message.setPass(NicknameStatus.S2.pass);
} else if (nickname.equals(dbNickname)){
message.setStatus(NicknameStatus.S202.status);
message.setStatusDesc(NicknameStatus.S202.statusDesc);
message.setPass(NicknameStatus.S202.pass);
} else {
message.setStatus(NicknameStatus.S200.status);
message.setStatusDesc(NicknameStatus.S200.statusDesc);
message.setPass(NicknameStatus.S200.pass);
}
play.Logger.info("checkNickname = " + Json.toJson(message));
return message;
}
// 驗證手機號碼
public VerificCheckMessage checkCellphone(String cellphone, String dbCellphone,
boolean isUsedCellphone) {
VerificCheckMessage message = new VerificCheckMessage();
message.setInputName("cellphone");
String cellphoneRegex = "[0-9]{4}[0-9]{6}";
if (cellphone == null || "".equals(cellphone)) {
message.setStatus(CellphoneStatus.S201.status);
message.setStatusDesc(CellphoneStatus.S201.statusDesc);
message.setPass(CellphoneStatus.S201.pass);
} else if (cellphone.equals(dbCellphone)){
message.setStatus(CellphoneStatus.S202.status);
message.setStatusDesc(CellphoneStatus.S202.statusDesc);
message.setPass(CellphoneStatus.S202.pass);
} else if (!cellphone.matches(cellphoneRegex)) {
message.setStatus(CellphoneStatus.S1.status);
message.setStatusDesc(CellphoneStatus.S1.statusDesc);
message.setPass(CellphoneStatus.S1.pass);
} else if (isUsedCellphone) {
message.setStatus(CellphoneStatus.S2.status);
message.setStatusDesc(CellphoneStatus.S2.statusDesc);
message.setPass(CellphoneStatus.S2.pass);
} else {
message.setStatus(CellphoneStatus.S200.status);
message.setStatusDesc(CellphoneStatus.S200.statusDesc);
message.setPass(CellphoneStatus.S200.pass);
}
play.Logger.info("checkCellphone = " + Json.toJson(message));
return message;
}
/**
* <pre>
* 傳入頁面會員明細資料
*
* </pre>
*/
public Map<String , VerificCheckMessage> checkMemberProfile(MemberProfile memberProfile ,Member member , MemberDetail memberDetail,
boolean isImg , boolean isUsedUsername , boolean isUsedCellphone ) {
String dbUsername = member.getUsername();
String dbHeaderPicLink = memberDetail != null ? memberDetail.getHeaderPicLink() : "";
String dbBirthday = memberDetail != null ? memberDetail.getBirthday() : "";
String dbCellphone = memberDetail != null ? memberDetail.getCellphone() : "" ;
String dbNickname = memberDetail != null ? memberDetail.getNickname() : "";
Map<String , VerificCheckMessage> results = new HashMap<String , VerificCheckMessage>();
results.put("headerPicLink", checkHeaderPicLink(memberProfile.getHeaderPicLink() , dbHeaderPicLink , isImg));
results.put("nickname", checkNickname(memberProfile.getNickname() , dbNickname));
results.put("username", checkEditUsername(memberProfile.getUsername() , dbUsername , isUsedUsername));
results.put("birthday", checkBirthday(memberProfile.getBirthday() , dbBirthday));
results.put("cellphone", checkCellphone(memberProfile.getCellphone() , dbCellphone , isUsedCellphone));
return results;
}
/**
* <pre>
* 整理會員明細資料
* </pre>
*/
public MemberDetail genMemberDetail(String memberNo, MemberProfile memberProfile) {
MemberDetail detail = new MemberDetail();
detail.setMemberNo(memberNo);
// 生日:2017/03/01 => 20170301
detail.setBirthday(memberProfile.getBirthday().replaceAll("/", ""));
detail.setCellphone(memberProfile.getCellphone());
detail.setHeaderPicLink(memberProfile.getHeaderPicLink());
detail.setNickname(memberProfile.getNickname());
return detail;
}
app.controllers.WebController.java
前面的類別準備,已經可以架構出後端需要的會員資料的檢核以及寫入,接下來就是撰寫主要每個欄位的流程與實作,會根據欄位的不同,會撈取會員資料或會員明細資料,以及前端所輸入的欄位,送的Utils_Signup進行欄位驗證,檢核完畢之後,會回傳Json格式,讓前端頁面知道該欄位的狀態,是否可以進行後續處理。
/**
* 進入修改會員個人資料頁面
*/
public Result editProfile(){
return ok(editProfile.render());
}
/**
* 即時載入會員明細個人資料
*/
public Result ajaxLoadMemberProfile(){
Utils_Session utilSsession = new Utils_Session();
MemberProfile memberProfile = new MemberProfile();
try{
String memberNo = utilSsession.getUserNo();
Member member = webService.findMemberByMemberNo(memberNo);
MemberDetail memberDetail = webService.findMemberDetailByMemberNo(memberNo);
memberProfile = new Utils_Signup().genMemberProfile(member , memberDetail);
} catch (Exception e) {
e.printStackTrace();
}
Logger.info("ajaxLoadMemberProfile = " + Json.toJson(memberProfile));
return ok(Json.toJson(memberProfile));
}
/**
* <pre>
* 修改會員個人資料動作
*
* Step 1 : 確認使用者填入的資料
* Step 2 : 確認無誤,寫入使用者資料
* Step 3 : 確認更新狀態,回傳更新寫入訊息
*
* </pre>
*/
public Result doEditProfile(){
Logger.info("start doEditProfile");
Date startTime = new Date();
boolean isAllPass = false;
String updateType = "";
String status = "";
String statusDesc = "";
String costTime = "";
String memberNo = "";
boolean update = false;
Map<String , VerificCheckMessage> verificResults = null;
Utils_Session utilSsession = new Utils_Session();
UpdateMessage updateMessage = new UpdateMessage();
Utils_Signup utilsSignup = new Utils_Signup() ;
try{
// Step 1
MemberProfile memberProfile = formFactory.form(MemberProfile.class).bindFromRequest().get();
memberNo = utilSsession.getUserNo();
Member member = webService.findMemberByMemberNo(memberNo);
MemberDetail memberDetail = webService.findMemberDetailByMemberNo(memberNo);
boolean isImg = new HttpHelper(ws).checkImgUrl(memberProfile.getHeaderPicLink());
boolean isUsedUsername = webService.checkMemberByUsername(memberProfile.getUsername());
boolean isUsedCellphone = webService.checkMemberDetailByCellphone(memberProfile.getCellphone());
verificResults = utilsSignup.checkMemberProfile(memberProfile , member , memberDetail ,
isImg , isUsedUsername , isUsedCellphone );
Logger.info("Step 1 檢驗結果 : " + Json.toJson(verificResults));
// Step 2
isAllPass = false;
for(String key : verificResults.keySet()){
isAllPass = verificResults.get(key).isPass();
if(!isAllPass){
break;
}
}
Logger.info("Step 2 全部欄位檢查後,是否允許可以更新與寫入會員資料 : " + isAllPass);
// Step 3
if(isAllPass){
// 寫入Member記錄檔,與更新使用者名稱
boolean insertMemberLogOk = webService.genMemberChangeLog(member) > 0 ? true : false;
boolean updateUseenameOk = webService.updateMemberUsername(memberNo , memberProfile.getUsername()) > 0 ? true : false ;
// 寫入MemberDetail與記錄檔
MemberDetail newMemberDetail = utilsSignup.genMemberDetail(memberNo , memberProfile);
boolean updateMemberDeatilOk = webService.genMemberDetail(newMemberDetail) > 0 ? true : false;
boolean insertMemberDetailLogOk = webService.genMemberDetailChangeLog(newMemberDetail) > 0 ? true : false;
Logger.info("Step 3 更新或寫入資料是否成功 : updateUseenameOk(更新使用者名稱) : " + updateUseenameOk +
" , insertMemberLogOk(寫入會員log檔) : " + insertMemberLogOk +
" , updateMemberDeatilOk(寫入或更新會員明細) : " + updateMemberDeatilOk +
" , insertMemberDetailLogOk(寫入會員明細log檔) : " + insertMemberDetailLogOk);
if(insertMemberLogOk && updateUseenameOk && updateMemberDeatilOk && insertMemberDetailLogOk){
status = UpdateMemberProfileStatus.S200.status;
statusDesc = UpdateMemberProfileStatus.S200.statusDesc;
update = UpdateMemberProfileStatus.S200.update;
} else {
status = UpdateMemberProfileStatus.SE1.status;
statusDesc = UpdateMemberProfileStatus.SE1.statusDesc;
update = UpdateMemberProfileStatus.SE1.update;
}
} else {
status = UpdateMemberProfileStatus.E1.status;
statusDesc = UpdateMemberProfileStatus.E1.statusDesc;
update = UpdateMemberProfileStatus.E1.update;
}
} catch (Exception e){
e.printStackTrace();
status = UpdateMemberProfileStatus.SE1.status;
statusDesc = UpdateMemberProfileStatus.SE1.statusDesc;
update = UpdateMemberProfileStatus.SE1.update;
} finally {
costTime = (new Date().getTime() - startTime.getTime()) + " ms";
updateType = UpdateType.updateMemberProfile;
updateMessage.setUpdateType(updateType);
updateMessage.setStatus(status);
updateMessage.setStatusDesc(statusDesc);
updateMessage.setUpdate(update);
updateMessage.setCostTime(costTime);
updateMessage.setVerificResults(verificResults);
}
Logger.info("finish doEditProfile = " + Json.toJson(updateMessage));
return ok(Json.toJson(updateMessage));
}
// 相依性注入 play.libs.ws.WSClient,可以用來呼叫別人寫好的Http服務
@Inject
WSClient ws;
/**
* 即時檢核圖片
*/
public Result ajaxCheckHeaderPicLink(){
String encodeUrl = "";
String headerPicLink = "";
String dbHeaderPicLink = "";
boolean isImg = false;
Utils_Session utilSsession = new Utils_Session();
EncAndDeCodeTool tool = new EncAndDeCodeTool();
try{
HttpHelper httpHelper = new HttpHelper(ws);
encodeUrl = request().getQueryString("headerPicLink");
headerPicLink = tool.urlAndBase64Decode(encodeUrl);
isImg = httpHelper.checkImgUrl(headerPicLink);
String memberNo = utilSsession.getUserNo();
MemberDetail detail = webService.findMemberDetailByMemberNo(memberNo);
dbHeaderPicLink = detail != null ? detail.getHeaderPicLink() : "";
} catch (Exception e){
e.printStackTrace();
}
VerificCheckMessage verificInfo = new Utils_Signup().checkHeaderPicLink(headerPicLink , dbHeaderPicLink, isImg);
return ok(Json.toJson(verificInfo));
}
/**
* 即時檢核使用者生日
*/
public Result ajaxCheckBirthday(){
String encBirthday = "";
String birthday = "";
String dbBirthday = "";
Utils_Session utilSsession = new Utils_Session();
EncAndDeCodeTool tool = new EncAndDeCodeTool();
try{
encBirthday = request().getQueryString("birthday");
birthday = tool.urlAndBase64Decode(encBirthday);
String memberNo = utilSsession.getUserNo();
MemberDetail detail = webService.findMemberDetailByMemberNo(memberNo);
dbBirthday = detail != null ? detail.getBirthday() : "";
} catch (Exception e){
e.printStackTrace();
}
VerificCheckMessage verificInfo = new Utils_Signup().checkBirthday(birthday, dbBirthday);
return ok(Json.toJson(verificInfo));
}
/**
* 即時檢核使用者名稱
*/
public Result ajaxCheckUsername(){
String encUsername = "";
String username = "";
String dbUsername = "";
boolean isUsedUsername = true;
Utils_Session utilSsession = new Utils_Session();
EncAndDeCodeTool tool = new EncAndDeCodeTool();
try {
encUsername = request().getQueryString("username");
username = tool.urlAndBase64Decode(encUsername);
isUsedUsername = webService.checkMemberByUsername(username);
dbUsername = webService.findMemberByMemberNo(utilSsession.getUserNo()).getUsername();
} catch (Exception e){
e.printStackTrace();
}
VerificCheckMessage verificInfo = new Utils_Signup().checkEditUsername(username , dbUsername, isUsedUsername);
return ok(Json.toJson(verificInfo));
}
/**
* 即時檢核暱稱
*/
public Result ajaxCheckNickname(){
String encNickname = "";
String dbNickname = "";
String nickname = "";
Utils_Session utilSsession = new Utils_Session();
EncAndDeCodeTool tool = new EncAndDeCodeTool();
try {
encNickname = request().getQueryString("nickname");
String memberNo = utilSsession.getUserNo();
nickname = tool.urlAndBase64Decode(encNickname);
MemberDetail detail = webService.findMemberDetailByMemberNo(memberNo);
dbNickname = detail != null ? detail.getNickname() : "";
} catch (Exception e){
e.printStackTrace();
}
VerificCheckMessage verificInfo = new Utils_Signup().checkNickname(nickname , dbNickname);
return ok(Json.toJson(verificInfo));
}
/**
* 即時檢核手機號碼
*/
public Result ajaxCheckCellphone(){
String encCellphone = "";
String cellphone = "";
String dbCellphone = "";
boolean isUsedCellphone = true;
Utils_Session utilSsession = new Utils_Session();
EncAndDeCodeTool tool = new EncAndDeCodeTool();
try {
encCellphone = request().getQueryString("cellphone");
cellphone = tool.urlAndBase64Decode(encCellphone);
String memberNo = utilSsession.getUserNo();
isUsedCellphone = webService.checkMemberDetailByCellphone(cellphone);
MemberDetail detail = webService.findMemberDetailByMemberNo(memberNo);
dbCellphone = detail != null ? detail.getCellphone() : "";
} catch (Exception e){
e.printStackTrace();
}
VerificCheckMessage verificInfo = new Utils_Signup().checkCellphone(cellphone, dbCellphone , isUsedCellphone);
return ok(Json.toJson(verificInfo));
}
conf.routes
當我們完成主要後端功能,後端服務最後一個任務是,把服務寫到routes上,讓前端頁面可以呼叫到WebController裡相關的服務。
# http://127.0.0.1:9000/web/editProfile
GET /web/editProfile controllers.WebController.editProfile()
# http://127.0.0.1:9000/web/doEditProfile
POST /web/editProfile controllers.WebController.doEditProfile()
# http://127.0.0.1:9000/web/ajaxLoadMemberProfile
GET /web/ajaxLoadMemberProfile controllers.WebController.ajaxLoadMemberProfile()
# http://127.0.0.1:9000/web/ajaxCheckHeaderPicLink
GET /web/ajaxCheckHeaderPicLink controllers.WebController.ajaxCheckHeaderPicLink()
# http://127.0.0.1:9000/web/ajaxCheckBirthday
GET /web/ajaxCheckBirthday controllers.WebController.ajaxCheckBirthday()
# http://127.0.0.1:9000/web/ajaxCheckUsername
GET /web/ajaxCheckUsername controllers.WebController.ajaxCheckUsername()
# http://127.0.0.1:9000/web/ajaxCheckNickname
GET /web/ajaxCheckNickame controllers.WebController.ajaxCheckNickname()
# http://127.0.0.1:9000/web/ajaxCheckCellphone
GET /web/ajaxCheckCellphone controllers.WebController.ajaxCheckCellphone()