iT邦幫忙

2025 iThome 鐵人賽

DAY 5
0
佛心分享-SideProject30

吃出一個SideProject!系列 第 5

Day5:Auth Service - 實作註冊功能(資料層)

  • 分享至 

  • xImage
  •  

今天我們大部分的內容會專注在整個註冊流程的資料層 (Data Layer),也就是負責與資料庫溝通的元件們。

一個完整的功能需要由許多不同的元件協力完成,因此在這之前,會先說明檔案架構,概覽這個過程需要建立的各元件,再接著建置EntityRepository

檔案結構說明

本專案採用常見的三層式架構,每層職責說明如下:

  • Controller:API端點,定義API接收與回傳。
  • Service:業務邏輯實現。
  • Repository:與資料庫進行溝通。

除此之外,在這個階段還會手動建立幾個重要的package:

  • Entity:用以定義用戶資料型態,並依此類別定義產生資料表。
  • Dto(Data Transfer Object):用來定義 API 的請求與回應的資料格式。
  • config:撰寫 Spring Security 等框架的組態設定,如定義密碼加密方式與 API path 權限控制。

因為開發階段使用容器啟動服務,因此還會有dockerfile與docker-compose的檔案出現在下述的樹狀架構中:

food-print-project
├─.idea
├─.env
├─docker-compose
├─pom
└─auth-service
    ├─pom
    ├─dockerfile
    ├─.mvn
    ├─src
    │  ├─main
    │  │  ├─java
    │  │  │  └─com
    │  │  │      └─twohands
    │  │  │          ├─config
    │  │  │          ├─controller
    │  │  │          ├─dto
    │  │  │          ├─entity
    │  │  │          ├─repository
    │  │  │          └─service
    │  │  │              └─Impl
    │  │  └─resources
    │  └─test
    │      └─java
    └─target
        ├─classes
        │  └─com
        │      └─twohands
        └─generated-sources
            └─annotations

Entity :定義使用者物件類別並產生資料表

Entity 用於定義物件屬性並產生對應的資料表。如同前幾天對 Spring Security 的介紹,我們會在這一步建立 UserEntity 的類別,其實作了 UserDetail 這個介面,讓使用者模型基於框架所定的屬性與方法延伸開發,細節如下:

...
@Entity
@Table(name = "users")
public class UserEntity implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false)
    private String password;
    
    @CreatedDate
    @Column(nullable = false, updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    @Column(nullable = false)
    private LocalDateTime updatedAt;

    // --- 以下為 UserDetails 介面的實作 ---

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // 暫時先給所有使用者相同的權限
        return List.of(new SimpleGrantedAuthority("ROLE_USER"));
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.email;
    }

    @Override
    public boolean isAccountNonExpired() { return true; }

    @Override
    public boolean isAccountNonLocked() { return true; }

    @Override
    public boolean isCredentialsNonExpired() { return true; }

    @Override
    public boolean isEnabled() { return true; }

    public Long getId() {
        return id;
    }

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

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

因為沒有特別的需求,基本上就是定義帳號 (Email) 與密碼,另外自動給予每個新使用者一組唯一的序號,作為primary key,避免email未來有更動的風險。以下針對 Annotation 用途進行說明:

  • @Entity

    • 告訴 JPA 將這個 UserEntity Class 視為一個資料庫實體來管理。JPA 會為它建立資料表、進行 CRUD 操作。
  • @Table(name = "users")

    • 指定 UserEntity 對應到資料庫中的資料表名稱為 users。(沒指定的話預設會使用 Class 的小寫名稱 userentity 作為資料表名。)
  • @Id

    • 指定這個屬性為這張資料表的 Primary Key。
  • @GeneratedValue(strategy = GenerationType.IDENTITY)

    • 告訴 JPA 這個 Primary Key 由資料庫自動產生的。
    • strategy 參數用來指定產生策略。IDENTITY 策略依賴於特定資料庫自動賦予primary key的功能,如PostgreSQL的SERIAL,或MySQL的AUTO_INCREMENT
  • @Column(nullable = false, unique = true)

    • @Column 用來針對特定欄位進行設定
    • nullable = false: 代表 email 這個欄位在資料庫中不允許是空值 (NULL)。
    • unique = true: 代表 email 欄位的值必須是唯一的,不能重複。
  • @CreatedDate@LastModifiedDate

    這是 JPA Auditing (稽核) 功能的一環,可以自動添加時間相關欄位內容。要啟用此功能,需要在 Config 中加上 @EnableJpaAuditing 的設定:

    ...
    @SpringBootApplication
    @EnableJpaAuditing
    public class AuthServiceApplication {
    	...
    }
    
    • @CreatedDate :在資料首次被新增至資料庫時,自動填入當前的時間。通常會搭配 @Column(updatable = false),確保這筆紀錄的創建時間永遠不會被更動。
    • @LastModifiedDate在資料每次被更新時,自動填入當前的時間。
  • UserDetail 提供的方法:

    在 Spring Security 的 UserDetails 介面中,除了 getUsername()getPassword()getAuthorities() 這三個核心方法外,還定義了四個用來檢查帳號狀態的方法。由於我們尚未規劃到那麼細緻的功能,先簡單回傳true,不檢核相關狀態:

方法 說明 使用場景
isAccountNonExpired() 帳號是否未過期 用於有時效性的帳號,例如試用會員或僅可使用 30 天的臨時帳號。
isAccountNonLocked() 帳號是否未被鎖定 可用於「多次登入失敗鎖定帳號」的功能。當偵測到使用者連續輸入錯誤密碼達到一定次數時,回傳 false,暫時鎖定帳號。
isCredentialsNonExpired() 憑證 (通常指密碼) 是否未過期 用於需要定期更換密碼的安全性要求。例如,系統強制使用者每 90 天更換一次密碼,若超過時間,此方法回傳 false,強制使用者進入重設密碼流程。
isEnabled() 帳號是否啟用 可用於 Email 驗證場景。使用者建立帳號後,此方法先回傳 false,待使用者點擊驗證信中的連結後,才將狀態改為 true,視為成功開通帳號。

以上介紹了可以用來產生對應資料表的Entity,讓我們建立一個跟資料庫溝通的管道:Repository

Repository :跟資料庫溝通的管道

Repository 可以被視為程式與資料庫溝通的管道,如果對於資料庫的查詢是常見、簡單的 CRUD,大部分會以JPA的概念進行實作。

本次先以email查詢為例,在Repository資料夾下建立了一個 AuthRepository的介面,該介面繼承自 JpaRepository,實作細節如下:

...
public interface AuthRepository extends JpaRepository<UserEntity, Long> {
    Optional<UserEntity> findByEmail(String email);
}
...

原理概述

一直對於 Spring Data Jpa 能以這麼簡單的方式讓我們與資料庫進行溝通感到很好奇,看了一些資料後我的理解是:

Hibernate 是按照JPA 規範實作的 ORM 框架,但若直接使用 Hibernate,開發人員發現仍需自行處理許多資料庫互動的重複邏輯。

因此, Spring Data JPA 在 Hibernate 之上又加了一層抽象層,讓我們可以僅透過一套命名規則,來實現常見的資料庫操作。

使用上,只要繼承 Spring Data JPA 提供的 JpaRepository,Spring Boot 啟動時,會掃描繼承 JpaRepository 的介面,為其生成代理物件。

當我們在程式中呼叫 Repository 介面上定義的方法(如 authRepository .findByEmail() ),該代理物件會攔截這次呼叫,根據命名規則生成並執行資料庫語法。


今天完成了註冊功能相關的資料層 (Entity 與 Repository)設定,明天,會以此基礎上繼續說明如何完成 DTO、Service 、 Controller 與 Config 設定的部分。


上一篇
Day4:Auth Service - 專案設定與依賴管理
下一篇
Day 6:Auth Service - 實作註冊功能(業務邏輯與API端點)
系列文
吃出一個SideProject!8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言