系統架構,採分層架構設計,分別為表現層、邏輯層、及資料層,Service 的應用在於邏輯層,此層包含所有的商業邏輯,如:檢查、事務、回調等,每層都有獨立職責,多層協同提供完整的功能, EP 5-2 增加了範例,並透過 JUnit 5 來驗證產出結果。
Table of Contents
Toggle前言
Web Service 是一種服務導向架構的技術,通過標準的 Web 協議提供服務,保證不同平台的應用服務可以相互操作,而 RESTful API ,是種風格設計,並非是一種標準,可以支援各個平台像是: Web 、 Android 、 iOS 等,界面與資料採分離設計。
Web Service
檔案目錄
./
+- build.gradle
+- src
+- main
+- java
+- org
+- ruoxue
+- spring_boot_168
+- sso
+- account
+- api
+- AccountAPI.java
+- ex
+- AccountException.java
+- model
+- Account.java
+- service
+- AccountService.java
+- AccountServiceImpl.java
Gradle
build.gradle
增加依賴,修改完後,點右鍵,Gradle -> Refresh Gradle Project 。
buildscript {
group 'org.ruoxue.spring-boot-168'
version = '0.0.1-SNAPSHOT'
ext {
gsonVersion = '2.10'
}
}
plugins {
id 'java-library'
}
repositories {
jcenter()
}
dependencies {
implementation "com.google.code.gson:gson:${gsonVersion}"
}
實作
Account.java
新增檔案,建立 model ,做為儲存資料庫的 Entity 及 HTTP 回應 JSON 格式。
package org.ruoxue.spring_boot_168.sso.account.model;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class Account {
/** 帳號 */
private String cid;
/** 名稱 */
private String name;
/** 密码 */
private String password;
/** 密码鹽 */
private String salt;
@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this, ToStringStyle.JSON_STYLE);
builder.appendSuper(super.toString());
builder.append("cid", cid);
builder.append("name", name);
builder.append("password", password);
return builder.toString();
}
}
AccountException.java
新增檔案,自定義例外,當發生錯誤時,拋出此例外,讓外層調用的服務,處理例外,如:記錄 Log。
package org.ruoxue.spring_boot_168.sso.account.ex;
public class AccountException extends RuntimeException {
private static final long serialVersionUID = 1759876856800045047L;
public AccountException() {
super();
}
public AccountException(String message) {
super(message);
}
public AccountException(Throwable cause) {
super(cause);
}
public AccountException(String message, Throwable cause) {
super(message, cause);
}
}
AccountService.java
建立 Service 介面,宣告 insertAccount 、 findByCid 、 findAll 、 updateAccount 、 deleteAccount 方法,此時並沒有任何實作。
package org.ruoxue.spring_boot_168.sso.account.service;
import org.ruoxue.spring_boot_168.sso.account.model.Account;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
/**
* 帳號服務
*/
public interface AccountService {
/**
* 新增帳號資料
*
* @param account
* @return
*/
Account insertAccount(Account account);
/**
* 取得帳號資料
* @param cid
* @return
*/
Account findByCid(String cid);
/**
* 取得所有帳號資料
* @param pageable
* @return
*/
Page<Account> findAll(Pageable pageable);
/**
* 修改帳號資料
* @param account
* @return
*/
Account updateAccount(Account account);
/**
* 刪除帳號資料
* @param account
* @return
*/
int deleteAccount(Account account);
}
AccountServiceiImpl.java
實作新增、查詢、修改、刪除帳號資料等,檢查輸入參數是否為 null,若是則拋出自定義 Exception ,若通過檢查,則傳回模擬資料。
package org.ruoxue.spring_boot_168.sso.account.service;
import java.util.ArrayList;
import java.util.List;
import org.ruoxue.spring_boot_168.sso.account.ex.AccountException;
import org.ruoxue.spring_boot_168.sso.account.model.Account;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
@Service
public class AccountServiceImpl implements AccountService {
public AccountServiceImpl() {
}
/**
* 新增帳號資料
*
* @param account
* @return
*/
@Override
public Account insertAccount(Account account) {
Account result = null;
try {
if (account == null) {
throw new AccountException("account 必須有值。");
}
result = account;
} catch (AccountException ex) {
throw ex;
} catch (Exception ex) {
throw new AccountException(ex);
}
return result;
}
/**
* 取得帳號資料
*
* @param cid
* @return
*/
@Override
public Account findByCid(String cid) {
Account result = null;
try {
if (cid == null) {
throw new AccountException("cid 必須有值。");
}
result = new Account();
result.setCid("ruoxue");
result.setName("player");
} catch (AccountException ex) {
throw ex;
} catch (Exception ex) {
throw new AccountException(ex);
}
return result;
}
/**
* 取得所有帳號資料
*
* @param pageable
* @return
*/
@Override
public Page<Account> findAll(Pageable pageable) {
Page<Account> result = null;
try {
if (pageable == null) {
throw new AccountException("pageable 必須有值。");
}
List<Account> list = new ArrayList<Account>();
Account account = new Account();
account.setCid("ruoxue");
account.setName("player");
list.add(account);
account = new Account();
account.setCid("ruoxue2");
account.setName("player2");
list.add(account);
result = new PageImpl<Account>(list, pageable, list.size());
} catch (AccountException ex) {
throw ex;
} catch (Exception ex) {
throw new AccountException(ex);
}
return result;
}
/**
* 修改帳號資料
*
* @param account
* @return
*/
@Override
public Account updateAccount(Account account) {
Account result = null;
try {
if (account == null) {
throw new AccountException("account 必須有值。");
}
result = account;
} catch (AccountException ex) {
throw ex;
} catch (Exception ex) {
throw new AccountException(ex);
}
return result;
}
/**
* 刪除帳號資料
*
* @param account
* @return
*/
@Override
public int deleteAccount(Account account) {
int result = 0;
try {
if (account == null) {
throw new AccountException("account 必須有值。");
}
result = 1;
} catch (AccountException ex) {
throw ex;
} catch (Exception ex) {
throw new AccountException(ex);
}
return result;
}
}
AccountAPI.java
調用 Service , 接收 POST 、 GET 、 PUT 、 DELETE 等 HTTP 協定的請求參數,實作 API 新增、查詢、修改、刪除帳號資料等功能,回應 JSON 格式。
package org.ruoxue.spring_boot_168.sso.account.api;
import java.util.Locale;
import org.ruoxue.spring_boot_168.sso.account.ex.AccountException;
import org.ruoxue.spring_boot_168.sso.account.model.Account;
import org.ruoxue.spring_boot_168.sso.account.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
@RestController
@Slf4j
public class AccountAPI {
@Autowired
private AccountService service;
private static final Gson gson = new Gson();
public AccountAPI() {
}
/**
* 新增帳號資料
*
* @param webRequest
* @param locale
* @param jsonParam
* @return
*/
@PostMapping(value = { "/api/sso/account" })
public ResponseEntity<Account> insert(WebRequest webRequest, Locale locale, @RequestBody String jsonParam) {
ResponseEntity<Account> result = null;
try {
Account account = service.insertAccount(gson.fromJson(jsonParam, Account.class));
result = ResponseEntity.ok().body(account);
} catch (AccountException ex) {
log.error(ex.getMessage(), ex);
result = ResponseEntity.notFound().build();
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
result = ResponseEntity.notFound().build();
}
return result;
}
/**
* 取得帳號資料
*
* @param webRequest
* @param locale
* @param cid
* @return
*/
@GetMapping(value = { "/api/sso/account/cid/{cid}" })
public ResponseEntity<Account> findByCid(WebRequest webRequest, Locale locale, @PathVariable String cid) {
ResponseEntity<Account> result = null;
try {
Account account = service.findByCid(cid);
result = ResponseEntity.ok().body(account);
} catch (AccountException ex) {
log.error(ex.getMessage(), ex);
result = ResponseEntity.notFound().build();
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
result = ResponseEntity.notFound().build();
}
return result;
}
/**
* 取得所有帳號資料
*
* @param webRequest
* @param locale
* @param cid
* @return
*/
@GetMapping(value = { "/api/sso/accounts" })
public ResponseEntity<Page<Account>> findAll(WebRequest webRequest, Locale locale, Pageable pageable,
@RequestParam int page, @RequestParam int size) {
ResponseEntity<Page<Account>> result = null;
try {
Page<Account> accounts = service.findAll(pageable);
result = ResponseEntity.ok().body(accounts);
} catch (AccountException ex) {
log.error(ex.getMessage(), ex);
result = ResponseEntity.notFound().build();
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
result = ResponseEntity.notFound().build();
}
return result;
}
/**
* 修改帳號資料
*
* @param webRequest
* @param locale
* @param name
* @return
*/
@PutMapping(value = { "/api/sso/account/cid/{cid}" })
public ResponseEntity<Account> update(WebRequest webRequest, Locale locale, @PathVariable String cid,
@RequestParam String name) {
ResponseEntity<Account> result = null;
try {
Account account = new Account();
account.setCid(cid);
account.setName(name);
Account ret = service.updateAccount(account);
result = ResponseEntity.ok().body(ret);
} catch (AccountException ex) {
log.error(ex.getMessage(), ex);
result = ResponseEntity.notFound().build();
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
result = ResponseEntity.notFound().build();
}
return result;
}
/**
* 刪除帳號資料
*
* @param webRequest
* @param locale
* @return
*/
@DeleteMapping(value = { "/api/sso/account/cid/{cid}" })
public ResponseEntity<Integer> delete(WebRequest webRequest, Locale locale, @PathVariable String cid) {
ResponseEntity<Integer> result = null;
try {
Account account = new Account();
account.setCid(cid);
int ret = service.deleteAccount(account);
result = ResponseEntity.ok().body(ret);
} catch (AccountException ex) {
log.error(ex.getMessage(), ex);
result = ResponseEntity.notFound().build();
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
result = ResponseEntity.notFound().build();
}
return result;
}
}
測試 Web Services
AccountServiceImplTest.java
新增單元測試,驗證是否符合預期 。
package org.ruoxue.spring_boot_168.sso.account.service;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.ruoxue.spring_boot_168.Application;
import org.ruoxue.spring_boot_168.sso.account.model.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = Application.class)
public class AccountServiceImplTest {
@Autowired
private AccountService service;
@Test
public void service() {
System.out.println(service);
assertNotNull(service);
}
@Test
public void insertAccount() {
Account account = new Account();
account.setCid("ruoxue");
account.setName("player");
account.setPassword("1111");
Account result = service.insertAccount(account);
System.out.println(result);
assertNotNull(result);
}
@Test
public void findByCid() {
Account result = service.findByCid("ruoxue");
System.out.println(result);
assertNotNull(result);
}
@Test
public void findAll() {
Pageable pageable = PageRequest.of(0, 10);
Page<Account> result = service.findAll(pageable);
System.out.println(result);
assertNotNull(result);
}
@Test
public void updateAccount() {
Account account = new Account();
account.setCid("ruoxue");
account.setName("player");
Account result = service.updateAccount(account);
System.out.println(result);
assertNotNull(result);
}
@Test
public void deleteAccount() {
Account account = new Account();
account.setCid("ruoxue");
int result = service.deleteAccount(account);
System.out.println(result);
assertTrue(result > 0);
}
}
insertAccount
測試方法上點右鍵執行 Run As -> JUnit Test ,查看 console。
{"cid":"ruoxue","name":"player","password":"1111"}
findByCid
測試方法上點右鍵執行 Run As -> JUnit Test ,查看 console。
{"cid":"ruoxue","name":"player","password":null}
findAll
測試方法上點右鍵執行 Run As -> JUnit Test ,查看 console。
Page 1 of 1 containing org.ruoxue.spring_boot_168.sso.account.model.Account instances
updateAccount
測試方法上點右鍵執行 Run As -> JUnit Test ,查看 console。
{"cid":"ruoxue","name":"player","password":null}
deleteAccount
測試方法上點右鍵執行 Run As -> JUnit Test ,查看 console。
1