iT邦幫忙

2021 iThome 鐵人賽

DAY 15
1

前面幾篇已經完成了資料庫的基本操作跟使用Thymeleaf 呈現頁面,接下來才真正要踏入Spring Boot 的世界,我們會圍繞在註冊、登入與會員中心三個基本功能,從最簡單的資料庫操作,逐步加上資料驗證、例外處理、登入日誌等功能。
記得相關的註釋在Day 09 - Spring Boot 常用註釋(下)都有提到,由於篇幅的關係,這邊就不闡述開發時的測試程式,主要講Controller 和Service 這兩層的內容,對測試有興趣的話可以去看Github。

控制器層

首先,要在控制器層的類別上新增註釋@Controller,宣告這是SpringMVC 的Controller 物件,才繼續往下設定請求的路徑,其實在上一篇Day 14 - Spring Boot & Thymeleaf已經建立了顯示註冊與登入頁面的Controller,就在裡面新增兩個POST 的方法,用來接收註冊與登入時的送出的表單。

@Controller
public class MemberAccountController {

	@Autowired
	private MemberAccountService memberAccountService;
	
	@RequestMapping(value = {"/", "/login"}, method = RequestMethod.GET)
	public String login(
			@ModelAttribute MemberAccount memberAccount,
			@ModelAttribute(value = "MESSAGE") String message) {
		
		return "login";
	}
	
	@RequestMapping(value = "/login", method = RequestMethod.POST)
	public String doLogin(
			@ModelAttribute MemberAccount memberAccount,
			HttpSession session, 
			RedirectAttributes redirectAttributes) {
		
		MemberAccountVO memberAccountVO = memberAccountService.login(memberAccount);
		if(memberAccountVO == null) {
			String message = memberAccountVO == null ? "帳號或密碼錯誤" : "";	
			redirectAttributes.addFlashAttribute("MESSAGE", message);
			return "redirect:login";
		}
		session.setAttribute("member", memberAccountVO);	
		return "redirect:information";
	}
	
	@RequestMapping(value = "/register", method = RequestMethod.GET)
	public String register(@ModelAttribute MemberAccountVO memberAccountVO) {
		
		return "register";
	}
	
	@RequestMapping(value = "/register", method = RequestMethod.POST)
	public String doRegister(
			@ModelAttribute MemberAccountVO memberAccountVO,
			RedirectAttributes redirectAttributes) {

		Optional<String> optional = memberAccountService.register(memberAccountVO);
		String message = optional.orElse("註冊成功");
		redirectAttributes.addFlashAttribute("MESSAGE", message);
		return "redirect:login";
	}
	
}

業務邏輯層

業務邏輯層的地方跟持久層都一樣,先定義好介面再撰寫實作該介面的具體類別,或許有些人會覺得這樣很麻煩,不能直接寫個類別嗎,這個原因其實並不複雜,推薦可以看一下Java 為什麼在Service層要使用Interface 這篇文章,作者有詳細解釋及實作講解為什麼要使用介面。

業務邏輯層介面

Day 06 - MVC 與三層架構已經提過的,三層架構的業務邏輯層與持久層都是對應MVC 架構中的M的部分,在業務邏輯層的介面這邊,可以用註解分類方法的用途

public interface MemberAccountService {

	// 業務邏輯
	public MemberAccountVO login(MemberAccount memberAccount);
	public Optional<String> register(MemberAccountVO memberAccountVO);
	
	// 資料庫操作
	public MemberAccount findMemberAccountByUsername(String username);	

}

業務邏輯層實作

在業務邏輯層的實作類別上,記得先新增註釋@Service,宣告這是業務處理類別,再依照邏輯需求使用@Autowired 注入所需要的物件,而實際上這邊應該要在每個方法上面都寫上很多註解標明這個方法的用途、引數以及回傳值,但這邊為了不讓文章太長就不寫了,就先記得在撰寫方法的邏輯前要列出該方法的邏輯順序

撰寫業務邏輯前

@Override
public MemberAccountVO login(MemberAccount memberAccount) {
	// TODO Auto-generated method stub
	// 檢查帳號是否存在

	// 使用資料庫鹽值對輸入密碼進行加密

	// 比對密碼是否相等

	// 取得對應Member 資料

	// 組合資料為MemberAccountVO

	return null;
}

完成

@Service
public class MemberAccountServiceImpl implements MemberAccountService {

	@Autowired
	private MemberAccountDao memberAccountDao;

	@Autowired
	private MemberService memberService;
	
	private String getMd5Password(String password, String salt) {
		// 對password + salt 進行三次加密
		String str = password + salt;
		for (int i = 0; i < 3; i++) {
			str = DigestUtils.md5DigestAsHex(str.getBytes()).toUpperCase();
		}
		return str;
	}
	
	@Override
	public MemberAccountVO login(MemberAccount memberAccount) {
		// TODO Auto-generated method stub
		// 檢查帳號是否存在
		MemberAccount data = memberAccountDao.findMemberAccountByUsername(memberAccount.getUsername());
		if(data == null) return null;

		// 使用資料庫鹽值對輸入密碼進行加密
		String md5Password = getMd5Password(memberAccount.getPassword(), data.getSalt());

		// 比對密碼是否相等
		if(!md5Password.equals(data.getPassword())) return null;
		
		// 取得對應Member 資料
		Member member = memberService.findMemberByMa_id(data.getId());
		if(member == null) return null;
		
		// 組合資料為MemberAccountVO
		MemberAccountVO memberAccountVO = new MemberAccountVO();
		memberAccountVO.setUsername(memberAccount.getUsername());
		memberAccountVO.setName(member.getName());
		return memberAccountVO;
	}

	@Override
	public Optional<String> register(MemberAccountVO memberAccountVO) {
		// TODO Auto-generated method stub
		// 驗證欄位是否填寫及格式
		if(!ValidFormat.isEmail(memberAccountVO.getUsername())) return Optional.of("帳號必須是Email 格式");
		if(!ValidFormat.isPassword(memberAccountVO.getPassword())) return Optional.of("密碼必須為長度6~16位碼大小寫英文加數字");
		if(!memberAccountVO.getPassword().equals(memberAccountVO.getCheckPassword())) return Optional.of("兩次輸入密碼不相符");
		
		// 檢查帳號是否重複註冊
		MemberAccount data = memberAccountDao.findMemberAccountByUsername(memberAccountVO.getUsername());
		if(data != null) return Optional.of("該帳號已被使用");
		
		// 產生鹽值
		String salt = UUID.randomUUID().toString().toUpperCase().replaceAll("-", "");
		
		// 密碼加密
		String md5Password = getMd5Password(memberAccountVO.getPassword(), salt);

		// 新增MemberAccount 資料
		MemberAccount memberAccount = new MemberAccount();
		memberAccount.setUsername(memberAccountVO.getUsername());
		memberAccount.setPassword(md5Password);
		memberAccount.setSalt(salt);
		memberAccount.setCreate_by(memberAccountVO.getUsername());
		memberAccount.setUpdate_by(memberAccountVO.getUsername());
		Integer id = memberAccountDao.insert(memberAccount);
		if(id == 0) return Optional.of("新增會員帳號時發生錯誤");

		// 新增Member 資料
		Member member = new Member();
		member.setMa_id(String.valueOf(id));
		member.setName(memberAccountVO.getName());
		member.setCreate_by(memberAccountVO.getUsername());
		member.setUpdate_by(memberAccountVO.getUsername());
		Integer result = memberService.insert(member);
		if(result == 0) return Optional.of("新增會員資料時發生錯誤");
		
		return Optional.empty();
	}

	@Override
	public MemberAccount findMemberAccountByUsername(String username) {
		// TODO Auto-generated method stub
		return memberAccountDao.findMemberAccountByUsername(username);
	}

}

Github

新增註冊與登入功能及其測試方法


上一篇
Day 14 - Spring Boot & Thymeleaf
下一篇
Day 16 - Spring Boot 資料驗證
系列文
誤打誤撞學了Spring Boot 還當了後端工程師30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言