Trong hầu hết các phần mềm thì người dùng luôn là trung tâm để phục vụ, hiểu được điều này, EzyPlatform đã đóng gói lại khá nhiều nghiệp vụ liên quan đến người dùng để các nhà phát triển có thể tái sử dụng.

Một số nghiệp vụ cơ bản liên quan người dùng

Với người dùng thì sẽ có một số nghiệp vụ cơ bản sau:

  1. Đăng nhập: EzyPlatform có lưu trữ các thông tin như tên đăng nhập, mật khẩu, email, số điện thoại, bạn có thể sử dung các thông tin này để xác thực người dùng.
  2. Quản lý thông tin liên hệ: Bao gồm chính là email và số điện thoại.
  3. Quản lý các trạng thái: Chưa được kích hoạt, đã được kích hoạt, đã bị lưu trữ, đã bị xoá thông tin hoặc là đã bị xoá vĩnh viễn.
  4. Quản lý danh sách người dùng: Thêm, sửa, đặt lại mật khẩu, tìm kiếm.
  5. Phân quyền: Quản lý các quyền, gán quyền cho người dùng.

Thiết kế cơ sở dữ liệu

Để đáp ứng được các nghiệp vụ kể trên, EzyPlatform sẽ cần một thiết kế cơ sở dữ liệu với các bảng như sau:

EzyPlatform quản lý quản trị viên người dùng.png

Các bảng này cũng tương đối giống với các bảng dùng để quản lý các quản trị viên:

  1. Bảng ezy_users: Dùng để quản lý thông tin cơ bản của người dùng, ví dụ tên đăng nhập, mật khẩu, email, số điện thoại ...
  2. Bảng ezy_user_meta: Bởi vì EzyPlatform phục vụ cho mọi loại phần mềm nên có thể trong các phần mềm, plugin đó cần thêm các thông tin bổ sung cho người dùng thì đây là bảng tiện ích có thể cho phép bổ sung thêm các thông tin của người dùng ngoài các thông tin cơ bản.
  3. Bảng ezy_user_access_tokens: Dùng để quản lý access token của người dùng, một người dùng có thể có nhiều access token nhưng một access token chỉ được sử dụng cho một người dùng.
  4. Bảng ezy_user_role_names: Dùng để quản lý các quyền của người dùng.
  5. Bảng ezy_user_roles: Đây là bảng trung gian, dùng để ánh xạ giữa người dùng và quyền.
  6. Bảng ezy_role_features: Đây là bảng quản lý các quyền và các API gắn với quyền đó, bạn có thể tham khảo bài viết phân quyền trong EzyPlatform trước đó để có thêm thông tin.
  7. Bảng ezy_user_keywords: Bởi vì sẽ có rất nhiều người dùng, nếu chúng ta tìm kiếm bằng từ khoá LIKE thì sẽ gây ra các vấn đề về hiệu năng, chính vì vậy EzyPlatform sinh ra bảng ezy_user_keywords để lưu trữ các từ khoá được giải (extract) ra từ tên đăng nhập, tên hiển thị, email và số điện thoại để hỗ trợ việc tìm kiếm người dùng nhanh hơn.

Mã nguồn SQL của các bảng sẽ như sau:

CREATE TABLE IF NOT EXISTS `ezy_users` (
    `id` bigint unsigned NOT NULL AUTO_INCREMENT,
    `uuid` varchar(128) COLLATE utf8mb4_unicode_520_ci NOT NULL,
    `username` varchar(128) COLLATE utf8mb4_unicode_520_ci NOT NULL,
    `display_name` varchar(250) COLLATE utf8mb4_unicode_520_ci,
    `password` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL,
    `email` varchar(120) COLLATE utf8mb4_unicode_520_ci,
    `phone` varchar(20) COLLATE utf8mb4_unicode_520_ci,
    `url` varchar(120) COLLATE utf8mb4_unicode_520_ci,
    `avatar_image_id` bigint,
    `cover_image_id` bigint,
    `activation_key` varchar(255) COLLATE utf8mb4_unicode_520_ci,
    `status` varchar(25) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'INACTIVATED',
    `created_at` datetime NOT NULL DEFAULT '2021-01-01 00:00:00',
    `updated_at` datetime NOT NULL DEFAULT '2021-01-01 00:00:00',
    PRIMARY KEY (`id`),
    UNIQUE KEY `key_uuid` (`uuid`),
    UNIQUE KEY `key_username` (`username`),
    UNIQUE KEY `key_email` (`email`),
    UNIQUE KEY `key_phone` (`phone`),
    UNIQUE KEY `key_activation_key` (`activation_key`),
    INDEX `index_status` (`status`),
    INDEX `index_created_at` (`created_at`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

CREATE TABLE IF NOT EXISTS `ezy_user_meta` (
    `id` bigint unsigned NOT NULL AUTO_INCREMENT,
    `user_id` bigint unsigned NOT NULL DEFAULT 0,
    `meta_key` varchar(255) COLLATE utf8mb4_unicode_520_ci DEFAULT NULL,
    `meta_value` varchar(500) COLLATE utf8mb4_unicode_520_ci,
    `meta_number_value` bigint NOT NULL default 0,
    PRIMARY KEY (`id`),
    INDEX `index_key_user_id` (`user_id`),
    INDEX `index_meta_key` (`meta_key`),
    INDEX `index_meta_value` (`meta_value`),
    INDEX `index_meta_number_value` (`meta_number_value`),
    INDEX `index_meta_key_value` (`meta_key`, `meta_value`),
    INDEX `index_meta_key_number_value` (`meta_key`, `meta_number_value`),
    INDEX `index_user_id_meta_key` (`user_id`, `meta_key`),
    INDEX `index_user_id_meta_key_value` (`user_id`, `meta_key`, `meta_value`),
    INDEX `index_user_id_meta_key_number_value` (`user_id`, `meta_key`, `meta_number_value`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

CREATE TABLE IF NOT EXISTS `ezy_user_keywords` (
    `id` bigint unsigned NOT NULL AUTO_INCREMENT,
    `user_id` bigint unsigned NOT NULL,
	`keyword` varchar(120) COLLATE utf8mb4_unicode_520_ci NOT NULL,
    `priority` int unsigned NOT NULL,
    `created_at` datetime NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `key_user_id_keyword` (`user_id`, `keyword`),
    INDEX `index_user_id` (`user_id`),
    INDEX `index_keyword` (`keyword`),
    INDEX `index_priority` (`priority`),
    INDEX `index_user_id_keyword_priority` (`user_id`, `keyword`, `priority`),
    INDEX `index_created_at` (`created_at`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

CREATE TABLE IF NOT EXISTS `ezy_user_access_tokens` (
    `id` varchar(256) NOT NULL,
    `user_id` bigint NOT NULL,
    `renewal_count` bigint NOT NULL,
    `status` varchar(25) COLLATE utf8mb4_unicode_520_ci,
    `created_at` datetime NOT NULL,
    `expired_at` datetime NOT NULL,
    PRIMARY KEY (`id`),
    INDEX `index_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

CREATE TABLE IF NOT EXISTS `ezy_user_role_names` (
    `id` bigint unsigned NOT NULL AUTO_INCREMENT,
    `name` varchar(45) NOT NULL COLLATE utf8mb4_unicode_520_ci NOT NULL,
    `display_name` varchar(60) COLLATE utf8mb4_unicode_520_ci NOT NULL,
    `priority` int NOT NULL DEFAULT 0,
    `created_at` datetime NOT NULL,
    `updated_at` datetime NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `key_name` (`name`),
    UNIQUE KEY `key_display_name` (`display_name`),
    INDEX `index_priority` (`priority`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

CREATE TABLE IF NOT EXISTS `ezy_user_roles` (
    `role_id` bigint unsigned NOT NULL,
    `user_id` bigint unsigned NOT NULL,
    `created_at` datetime NOT NULL,
    PRIMARY KEY (`role_id`, `user_id`),
    INDEX `index_role_id` (`role_id`),
    INDEX `index_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

CREATE TABLE IF NOT EXISTS `ezy_role_features` (
    `role_id` bigint unsigned NOT NULL,
    `target` varchar(25) COLLATE utf8mb4_unicode_520_ci NOT NULL,
    `feature` varchar(120) COLLATE utf8mb4_unicode_520_ci NOT NULL,
    `feature_uri` varchar(300) COLLATE utf8mb4_unicode_520_ci NOT NULL,
    `feature_method` varchar(25) COLLATE utf8mb4_unicode_520_ci NOT NULL,
    `created_at` datetime NOT NULL,
    PRIMARY KEY (`role_id`, `target`, `feature`, `feature_uri`, `feature_method`),
    INDEX `index_role_id` (`role_id`),
    INDEX `index_role_id_target` (`role_id`, `target`),
    INDEX `index_feature_uri_method` (`target`, `feature_uri`, `feature_method`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

Chúng tôi tạo sẵn rất nhiều index, nếu trong quá trình vận hành bạn thấy không cần thiết, bạn có thể xoá bớt những index này hoặc bổ sung các index khác cần thiết.

Trên thực tế một người dùng có rất nhiều thông tin, chính vì vậy ngoài cách sử dụng bảng ezy_user_meta bạn cũng có thể tạo ra bảng mới giống như cách mà ezycrm đã làm.

Thiết kế lớp

Cũng tương tự như quản lý quản trị viên, sẽ có rất nhiều lớp được sinh ra để quản lý quản trị viên bao gồm:

  1. Các lớp Entity.
  2. Các lớp Model.
  3. Các lớp Repository.
  4. Các lớp Service.
  5. Các lớp Controller.

Các lớp Entity

Tương ứng với mỗi bảng sẽ lại có một lớp Entity được sinh ra:

  1. User.
  2. UserMeta.
  3. UserAccessToken.
  4. UserRoleName.
  5. UserRoleId và UserRole.
  6. RoleFeatureId và RoleFeature.
  7. UserKeyword.

Các lớp Repostiory

Tương ứng với các Entity lại có các lớp Repository được sinh ra:

  1. UserRepository, AdminUserRepository, WebUserRepository và SocketUserRepository.
  2. UserMetaRepository, AdminUserMetaRepository, AdminUserMetaTransactionalRepository, SocketUserMetaRepository và SocketUserMetaTransactionalRepository.
  3. UserAccessTokenRepository, AdminUserAccessTokenRepository, WebUserAccessTokenRepository và SocketUserAccessTokenRepository.
  4. UserRoleNameRepository, AdminUserRoleNameRepository và WebUserRoleNameRepository.
  5. UserRoleRepository, AdminRoleRepository và WebUserRoleRepository.
  6. RoleFeatureRepository, AdminRoleFeatureRepository và WebRoleFeatureRepository.
  7. UserKeywordRepository, UserKeywordTransactionalRepository, AdminUserKeywordRepository, AdminUserKeywordTransactionalRepository, WebUserKeywordRepository và WebUserKeywordTransactionalRepository.

Các lớp Model

Cũng tương ứng với các Entity được sinh ra thì sẽ có các model được sinh ra:

  1. UserModel và UserNameModel.
  2. UserAccessTokenModel.
  3. UserRoleNameModel.
  4. UserRoleModel.

Các lớp Service

Tương ứng với các lớp Repository sẽ có các lớp Service được sinh ra:

  1. UserService, DefaultUserService, AdminUserService, WebUserService, SocketUserService.
  2. UserMetaService, DefaultUserMetaService, AdminUserMetaService, WebUserMetaService và SocketUserMetaService.
  3. UserAccessTokenService. DefaultUserAccessTokenService, AdminUserAccessTokenService, SocketAdminAccessTokenService.
  4. UserRoleService, DefaultUserRoleService, AdminUserRoleService, WebUserRoleService.
  5. RoleFeatureService, DefaultRoleFeatureService, AdminRoleFeatureService và WebRoleFeatureService.
  6. UserKeywordService, AdminUserKeywordService và WebUserKeywordService.

Các lớp controller dùng để quản lý người dùng bao gồm:

  1. AdminUsersController: Cung cấp các màn hình quản lý người dùng.
  2. AdminApiUsersController: Cung cấp các API cho nghiệp vụ quản trị người dùng.

Có rất nhiều lớp nên khá khó để vẽ thiết kế chung vào một hình, vậy mình sẽ vẽ đại diện các lớp liên quan đến bảng ezy_users, tuy nhiên các bảng khác cũng có thiết kế lớp gần giống như vậy.

EzyPlatform quản lý quản trị viên người dùng thiet ke lop.png

Cũng tương tự như quản lý các quản trị viên, sở dĩ sinh ra nhiều lớp thế này là do một số lớp được sử dụng chung giữa cả admin, web và socket nên chúng chưa được chỉ định là singleton mà phải khi vào từng thành phần cụ thể mới có các lớp cụ thể ví dụ như AdminApiUsersController hay AdminAdminRepository để được chỉ định là singleton.

Quản trị các quản trị trị viên thông qua giao diện admin

Từ giao diện quản trị viên bạn có thể:

  1. Xem danh sách người dùng.
  2. Thêm mới một người dùng.
  3. Sửa đổi một người dùng.
  4. Cấp quyền cho người dùng.

Bạn có thể tìm thấy các menu quản lý người dùng ở gần cuối sidebar:

Screenshot 2024-11-29 at 17.35.31.png

Sau đó bạn có thể chọn để xem danh sách người dùng:

Screenshot 2024-11-29 at 17.37.25.png

Bạn có thể thực hiện các hành động hàng loạt trên danh sách người dùng:

Screenshot 2024-11-29 at 17.37.35.png

Bạn có thể thêm mới một người dùng:

Screenshot 2024-11-29 at 17.37.43.png

Xem thông tin một người dùng:

Screenshot 2024-11-29 at 17.38.02.png

Hoặc chỉnh sửa một người dùng:

Screenshot 2024-11-29 at 17.38.09.png

Hoặc cấp quyền cho người dùng nếu muốn:

Screenshot 2024-11-29 at 17.38.20.png

Tổng kết

Nghiệp vụ quản lý người dùng là rất cơ bản tuy nhiên cũng đặt ra rất nhiều thách thức cho các nhà phát triển. Việc EzyPlatform đóng gói lại các tính năng quản lý người dùng sẽ giúp các nhà phát triển tiết kiệm được rất nhiều thời gian, ngoài ra nó còn giúp sản phẩm của nhà phát triển sớm được định hình và hoàn thiện một cách nhanh chóng hơn.