RIKKEI BANK là hệ thống quản lý ngân hàng số được xây dựng bằng Java Spring Boot, cung cấp RESTful API cho các chức năng quản lý người dùng, tài khoản ngân hàng, xác thực JWT, chuyển tiền, sao kê giao dịch, định danh điện tử eKYC và ghi log kiểm toán bằng AOP.
Dự án được thiết kế theo kiến trúc Monolithic, chia tầng rõ ràng:
Controller -> Service -> Repository -> Database
Hệ thống hoạt động theo mô hình Stateless Backend. Client như Web, Mobile hoặc Postman sẽ gửi request tới RESTful API và xác thực bằng JWT Access Token.
- Java 17
- Spring Boot
- Spring Web / Spring WebMVC
- Spring Security
- Spring Data JPA
- MySQL
- JWT Access Token / Refresh Token
- BCrypt Password Encoder
- Spring AOP
- Cloudinary Upload
- JUnit 5
- Mockito
- Gradle
- Postman
- Đăng ký tài khoản khách hàng.
- Đăng nhập và cấp JWT.
- Cấp lại Access Token bằng Refresh Token.
- Đăng xuất và đưa Access Token vào blacklist.
- Quên mật khẩu cơ bản.
- Quản lý người dùng có phân trang.
- Xem chi tiết người dùng.
- Cập nhật thông tin người dùng.
- Khóa tài khoản người dùng.
- Tự động tạo tài khoản ngân hàng khi đăng ký.
- Xem danh sách tài khoản theo user.
- Xem chi tiết tài khoản.
- Vấn tin số dư.
- Đổi mã PIN giao dịch.
- Chuyển tiền nội bộ.
- Kiểm tra tài khoản nguồn và tài khoản đích.
- Kiểm tra mã PIN giao dịch.
- Kiểm tra số dư trước khi chuyển tiền.
- Không cho customer chuyển tiền từ tài khoản không thuộc về mình.
- Sử dụng
@Transactionalđể đảm bảo toàn vẹn dữ liệu. - Lưu lịch sử giao dịch.
- Xem sao kê giao dịch.
- Phân biệt giao dịch tiền vào
CREDITvà tiền raDEBIT.
- Tạo hồ sơ KYC khi khách hàng đăng ký tài khoản.
- Customer upload ảnh CCCD lên Cloudinary.
- Lưu URL ảnh vào database.
- Staff/Admin xem danh sách hồ sơ KYC.
- Staff/Admin duyệt hồ sơ KYC.
- Staff/Admin từ chối hồ sơ KYC.
- Khi duyệt thành công:
KycProfile.status = CONFIRM,User.isKyc = true. - Khi từ chối:
KycProfile.status = REJECT,User.isKyc = false.
- Ghi log chuyển tiền thành công.
- Ghi log chuyển tiền thất bại.
- Ghi log upload KYC.
- Ghi log duyệt/từ chối KYC.
- Ghi log đổi mã PIN.
- Ghi log thời gian thực hiện các service.
- Lưu audit log vào bảng
audit_logs. - Admin xem danh sách audit log có phân trang.
| Role | Quyền |
|---|---|
ROLE_ADMIN |
Quản trị hệ thống, truy cập API admin, xem audit log |
ROLE_STAFF |
Truy cập API staff, duyệt/từ chối hồ sơ KYC |
ROLE_CUSTOMER |
Upload KYC, xem số dư, chuyển tiền, xem sao kê, đổi PIN |
| API | Quyền |
|---|---|
/api/v1/auth/** |
Public |
/api/v1/admin/** |
ADMIN |
/api/v1/staff/** |
STAFF, ADMIN |
/api/v1/customer/** |
CUSTOMER |
| Các API khác | Authenticated |
POST /api/v1/auth/register
POST /api/v1/auth/login
POST /api/v1/auth/refresh
POST /api/v1/auth/logout
POST /api/v1/auth/forgot-passwordGET /api/v1/admin/dashboard
GET /api/v1/admin/audit-logs?page=0&size=10GET /api/v1/staff/dashboard
GET /api/v1/staff/kyc?page=0&size=10
PUT /api/v1/staff/kyc/{id}/approve
PUT /api/v1/staff/kyc/{id}/rejectGET /api/v1/customer/dashboard
POST /api/v1/customer/kyc/upload
PUT /api/v1/customer/accounts/{accountId}/pin
POST /api/v1/customer/transactions/transfer
GET /api/v1/customer/accounts/{accountId}/transactions?page=0&size=10GET /api/v1/users?page=0&size=10
GET /api/v1/users/{id}
PUT /api/v1/users/{id}
DELETE /api/v1/users/{id}GET /api/v1/users/{userId}/accounts
GET /api/v1/accounts/{accountId}
GET /api/v1/accounts/{accountId}/balanceTạo database MySQL:
CREATE DATABASE rikkei_bank_db;Cấu hình trong src/main/resources/application.properties:
spring.application.name=rikkei-bank
spring.datasource.url=jdbc:mysql://localhost:3306/rikkei_bank_db?createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=123456$
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=truejwt.secret=rikkei-bank-secret-key-rikkei-bank-secret-key-256bit-very-secure
jwt.access-token-expiration=300000
jwt.refresh-token-expiration=86400000Ý nghĩa:
jwt.access-token-expiration=300000: Access Token hết hạn sau 5 phút.jwt.refresh-token-expiration=86400000: Refresh Token hết hạn sau 1 ngày.
spring.servlet.multipart.max-file-size=5MB
spring.servlet.multipart.max-request-size=10MBThêm thông tin Cloudinary vào application.properties:
cloudinary.cloud-name=your_cloud_name
cloudinary.api-key=your_api_key
cloudinary.api-secret=your_api_secretAPI upload ảnh eKYC:
POST /api/v1/customer/kyc/uploadCách gửi trong Postman:
Body -> form-data
key: file
type: File
value: chọn ảnh CCCD
Khi chạy project, hệ thống tự tạo tài khoản mẫu bằng DataSeeder:
| Username | Password | Role |
|---|---|---|
admin |
123456 |
ROLE_ADMIN |
staff |
123456 |
ROLE_STAFF |
customer_test |
123456 |
ROLE_CUSTOMER |
Mỗi tài khoản mẫu được tạo kèm một account ngân hàng có số dư ban đầu để test chuyển tiền.
Client gửi thông tin đăng ký
-> Hệ thống tạo User role CUSTOMER
-> Hệ thống tạo Account mặc định
-> Hệ thống tạo KycProfile trạng thái PENDING
-> Trả về thông tin user
Client gửi username/password
-> AuthenticationManager xác thực
-> Tạo Access Token
-> Tạo Refresh Token
-> Lưu Refresh Token vào database
-> Trả token về client
Client gửi Access Token cần logout
-> Hệ thống lưu Access Token vào bảng token_blacklist
-> JwtAuthenticationFilter kiểm tra blacklist ở các request sau
-> Nếu token nằm trong blacklist thì trả về 401 Unauthorized
Customer gửi fromAccountNumber, toAccountNumber, amount, transactionPin
-> Kiểm tra tài khoản nguồn tồn tại
-> Kiểm tra tài khoản đích tồn tại
-> Kiểm tra tài khoản nguồn thuộc về customer đang đăng nhập
-> Kiểm tra PIN giao dịch
-> Kiểm tra số dư
-> Trừ tiền tài khoản nguồn
-> Cộng tiền tài khoản đích
-> Lưu Transaction
-> AOP ghi Audit Log
Customer gửi accountId
-> Kiểm tra account thuộc customer đang đăng nhập
-> Query transaction theo fromAccountId hoặc toAccountId
-> Nếu accountId là fromAccount thì direction = DEBIT
-> Nếu accountId là toAccount thì direction = CREDIT
-> Trả danh sách giao dịch có phân trang
Customer upload ảnh CCCD
-> Ảnh được upload lên Cloudinary
-> Cloudinary trả secure_url
-> Hệ thống lưu secure_url vào idCardFrontUrl
-> KycProfile.status = PENDING
-> Staff/Admin duyệt hoặc từ chối hồ sơ
{
"success": true,
"message": "Thành công",
"data": {}
}{
"success": false,
"message": "Thông báo lỗi",
"data": null
}Dự án có tối thiểu 10 unit test.
- Chuyển tiền thành công.
- Chuyển tiền sai PIN.
- Chuyển tiền không đủ số dư.
- Duyệt KYC thành công.
- Từ chối KYC thành công.
- Register trả về HTTP 201.
- Login trả về HTTP 200.
- Refresh token trả về HTTP 200.
- Logout trả về HTTP 200.
- Forgot password trả về HTTP 200.
Chạy test:
gradlew testAudit log được xử lý bằng Spring AOP để tách logic ghi log khỏi logic nghiệp vụ chính.
Các hành động được ghi log:
TRANSFER- chuyển tiền thành công/thất bại.UPLOAD_KYC- upload ảnh KYC.APPROVE_KYC- duyệt hồ sơ KYC.REJECT_KYC- từ chối hồ sơ KYC.CHANGE_PIN- đổi mã PIN giao dịch.
Admin xem audit log:
GET /api/v1/admin/audit-logs?page=0&size=10| Mã FR | Chức năng | Trạng thái |
|---|---|---|
| FR-01 | Login JWT | OK |
| FR-02 | Refresh Token | OK |
| FR-03 | Logout/Revoke Token bằng database blacklist | OK |
| FR-04 | Register + eKYC | OK |
| FR-05 | User/Account CRUD + phân trang | OK |
| FR-06 | Vấn tin số dư | OK |
| FR-07 | Chuyển tiền | OK |
| FR-08 | Sao kê | OK |
| FR-09 | Duyệt KYC | OK |
| FR-10 | Đổi PIN / Quên mật khẩu | OK |
| FR-11 | AOP Audit Log | OK |
| FR-12 | Unit Test | OK |
| FR-13 | Redis TokenBlacklist | Chưa triển khai |
Hiện tại hệ thống chưa sử dụng Redis để lưu TokenBlacklist. Thay vào đó, hệ thống đang sử dụng bảng token_blacklist trong MySQL để lưu các Access Token đã đăng xuất, phục vụ chức năng Logout/Revoke Token.
Khi người dùng logout, Access Token sẽ được lưu vào database. Ở các request tiếp theo, JwtAuthenticationFilter sẽ kiểm tra token có nằm trong blacklist hay không. Nếu có, hệ thống trả về HTTP 401 Unauthorized.
Phần Redis là yêu cầu nâng cao nhằm tối ưu hiệu năng trong các dự án thực tế, giúp giảm tải truy vấn database và tránh tắc nghẽn cổ chai. Do giới hạn thời gian triển khai, phần Redis TokenBlacklist chưa được thực hiện trong phiên bản hiện tại.
- Sử dụng JWT Stateless Authentication.
- Access Token ngắn hạn, Refresh Token dài hạn.
- Logout bằng cơ chế Token Blacklist lưu trong MySQL database.
- Password và mã PIN giao dịch được mã hóa bằng BCrypt.
- Chuyển tiền sử dụng
@Transactionalđể đảm bảo toàn vẹn dữ liệu. - Chống chuyển tiền từ tài khoản không thuộc về user đăng nhập.
- Sao kê truy vấn cả tài khoản gửi và tài khoản nhận.
- AOP được dùng để tách logic audit log khỏi logic nghiệp vụ.
- Audit log được lưu vào database.
- Upload ảnh eKYC qua Cloudinary.
- Có phân quyền ADMIN / STAFF / CUSTOMER rõ ràng.
- Có unit test cho Service và Controller.
Nên demo theo thứ tự:
- Login admin/staff/customer.
- Register customer mới.
- Customer upload ảnh KYC.
- Staff xem danh sách KYC.
- Staff duyệt KYC.
- Customer chuyển tiền.
- Customer xem sao kê CREDIT/DEBIT.
- Customer đổi PIN.
- Customer logout.
- Dùng lại token cũ sau logout để kiểm tra 401.
- Admin xem audit log.
- Chạy unit test.
Bước 1: Clone project hoặc mở project trong IntelliJ IDEA.
Bước 2: Tạo database MySQL:
CREATE DATABASE rikkei_bank_db;Bước 3: Sửa cấu hình trong application.properties:
spring.datasource.username=root
spring.datasource.password=your_password
cloudinary.cloud-name=your_cloud_name
cloudinary.api-key=your_api_key
cloudinary.api-secret=your_api_secretBước 4: Chạy project:
gradlew bootRunBước 5: Test API bằng Postman.
Dự án được thực hiện phục vụ bài tập Java Web Service RESTful API - Hệ thống quản lý ngân hàng RIKKEI BANK.