Bài viết được sự cho phép của tác giả Kien Dang Chung
Trong bài trước chúng ta đã làm quen với khái niệm câu lệnh (directive) trong Vue, với mỗi ngôn ngữ các cú pháp in, điều kiện, vòng lặp là những cú pháp cơ bản nhất. Bài viết này sẽ đi sâu vào các câu lệnh điều kiện là những chỉ thị có điều kiện giúp cho bộ biên dịch của Vue.js có thể ra quyết định khi nào thì sinh mã HTML như thế này, khi nào thì làm việc khác… Một câu hỏi đặt ra, trong biểu thức Javascript chúng ta có thể đưa vào điều kiện, vậy cần gì đến các câu lệnh v-if, v-show?
Sử dụng câu lệnh v-if trong các thẻ HTML cho phép render các phần tử và nội dung theo các điều kiện dựa trên dữ liệu thuộc tính hoặc các biến trong Javascript. Ví dụ, người dùng nhập vào họ tên, giới tính, hiển thị câu chào thích hợp với các màu sắc theo giới tính.
<!DOCTYPE html><html><head><title>Ví dụ về v-if - Allaravel.com</title></head><body><div id="app"><h1 v-if="user.gender == 1 && user.name != null" style="color: red;">Chào anh {{ user.name }}</h1><h1 v-else-if="user.gender == 2 && user.name != null" style="color: green">Chào chị {{ user.name }}</h1><h1 v-else style="color: blue">Ví dụ về v-if</h1><br><label>Họ và tên</label><input v-model="user.name"placeholder="Nhập vào tên bạn"><br><label>Giới tính</label><select v-model="user.gender"><option value="1">Nam</option><option value="2">Nữ</option></select></div><script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script><script type="text/javascript">newVue({
el:'#app',
data:{
user:{}}})</script></body></html>
Ví dụ trên khá là dễ hiểu, khi tên người dùng không rỗng và giới tính nam được lựa chọn, trình biên dịch sẽ kiểm tra và thấy thẻ <h1> dưới đây thỏa mãn:
Giống như các ngôn ngữ lập trình, cấu trúc rẽ nhánh luôn có các kiểm tra điều kiện khác là else và elseif, Vue.js cũng vậy, nó có các câu lệnh v-else, v-else-if.
v-show
Một tùy chọn khác để render có điều kiện là sử dụng câu lệnh v-show, nó khác với v-if ở chỗ v-show render tất cả các phần tử html và sau đó các phần tử này hiển thị hay không thông qua thuộc tính CSS display:none hoặc không có thuộc tính này. Ví dụ ở trên được viết lại như sau:
<!DOCTYPE html><html><head><title>Ví dụ về v-show - Allaravel.com</title></head><body><div id="app"><h1 v-show="user.gender == 1 && user.name != null" style="color: red;">Chào anh {{ user.name }}</h1><h1 v-show="user.gender == 2 && user.name != null" style="color: green">Chào chị {{ user.name }}</h1><h1 v-show="user.name == null" style="color: blue">Ví dụ về v-show</h1><br><label>Họ và tên</label><input v-model="user.name"placeholder="Nhập vào tên bạn"><br><label>Giới tính</label><select v-model="user.gender"><option value="1">Nam</option><option value="2">Nữ</option></select></div><script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script><script type="text/javascript">newVue({
el:'#app',
data:{
user:{}}})</script></body></html>
Chạy ví dụ này và xem mã nguồn HTML bạn sẽ thấy khác so với ví dụ đầu là cả ba thẻ <h1> đều được tạo ra nhưng khi chưa nhập liệu hai thẻ <h1> đầu có thuộc tính display:none. Khi nhập liệu các giá trị thì các thẻ h1 này có thể được thêm hoặc bớt đi thuộc tính display:none để ẩn đi hoặc hiện lên cho phù hợp.
Bài tập
Thiết kế một form đăng nhập gồm địa chỉ email và mật khẩu, nếu dữ liệu nhập vào như sau:
Địa chỉ email: test@allaravel.com
Mật khẩu: 123456
Hiển thị lời chào “Bạn đã đăng nhập thành công”, nếu không hiển thị lời cảnh báo “Thông tin đăng nhập sai, vui lòng thử lại”.
Bạn nên thực hiện các bài tập đơn giản này, nó giúp cho việc nhớ kiến thức lâu và vận dụng kiến thức tốt hơn. Cố gắng đừng xem lời giải nếu bạn có thể tự thực hiện được.
Lời giải
<!DOCTYPE html><html><head><title>Bài tập sử dụng v-if Vuejs - Allaravel.com</title></head><body><div id="app"><h1 v-if="user.email == 'test@allaravel.com' && user.password == '123456'" style="color: green;">Chào mừng {{ user.email }} đã đăng nhập thành công!</h1><h1 v-if="(user.email != 'test@allaravel.com' && user.email != null) || (user.password != '123456' && user.password != null)" style="color: red;">Thông tin đăng nhập sai, vui lòng thử lại!</h1><br><div v-if="user.email != 'test@allaravel.com' || user.password != '123456'"><label>Địa chỉ email</label><input type="text"v-model="user.email"><br><label>Mật khẩu</label><input type="password"v-model="user.password"></div></div><script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script><script type="text/javascript">newVue({
el:'#app',
data:{
user:{}}})</script></body></html>
Bài viết được sự cho phép của tác giả Edward Thien Hoang
CÁC NGUYÊN LÝ THIẾT KẾ PACKAGE
Cùng điểm qua 1 số nguyên tắc đóng gói (packaging principle) của Robert C. Martin:
Nếu tôi hỏi bạn: “tại sao phải phân loại các class vào các package” bạn có thể sẽ trả lời: “để dễ quản lý”. Tất nhiên câu trả lời này là đúng, nhưng chưa giúp ích được gì nhiều. Và câu chuyện của chúng ta sẽ còn kéo dài hơn thế.
Phân loại là công việc mà chúng ta rất hay làm trong đời sống. Ví dụ, một người giữ xe thường sắp xe ga, xe số riêng, ngoài mục đích dễ tìm xe còn mục đích kinh tế: xe ga đắt tiền nên sẽ cần phải để mắt hơn.
Công việc phát triển phần mềm cũng tương tự như vậy. Nhiệm vụ của package không phải là để dễ tìm class. Tất nhiên với package thì việc tìm class có thể tiện lợi hơn đôi chút nhưng nó không quá quan trọng, nhất là các IDE ngày nay đều hỗ trợ tìm class trong nháy mắt. Một phần mềm hạng trung khoảng trăm class đã có thể rất phức tạp. Sở dĩ chúng phức tạp vì chúng có những mối quan hệ phụ thuộc lẫn nhau. Kiểm soát tốt những sự phụ thuộc này là mục tiêu sống còn của phần mềm. Công việc này quả thực là rất khó khăn với một số lượng lớn class. Do đó, chúng ta cần gom class vào các package. Số lượng package hiển nhiên sẽ ít hơn rất nhiều so với số lượng class. Việc kiểm soát sự phụ thuộc giữa một số lượng ít các package rõ ràng là thuận tiện hơn. Hơn nữa nó cũng giúp chúng ta có một cái nhìn khái quát về tổng thể phần mềm hơn là đi sâu vào class, vì class là quá nhỏ, quá chi tiết.
Nhưng chia class vào package như thế nào cho hiệu quả?
Granularity. Mỗi package cần phải đủ nhỏ vì nếu nó quá to thì sẽ rất tội cho những package khác chỉ phụ thuộc vào một phần của nó.
Stability. Mỗi khi có sự thay đổi xảy ra thì số lượng package bị tác động càng ít càng tốt.
Uncle Bob đưa ra 6 nguyên lý để đảm bảo tính hiệu quả của việc chia class.
Một phần mềm tốt thì cần phải dễ reuse. Một người nào đó muốn reuse các class của chúng ta, nhưng vì các class này lại được đóng vào các package, cho nên người ấy sẽ phải phụ thuộc vào nguyên các package ấy. Số lượng các class dư thừa (đối với người ấy) cần phải ít nhất có thể. Lý tưởng là zero (equivalence). Đây chính là nội dung của nguyên lý Reuse/Release Equivalence.
The classes in a component are reused together. If you reuse one of the classes in a component, you reuse them all.
Cái người mà đang muốn reuse các class của chúng ta, anh ấy muốn số lượng package phải phụ thuộc là ít nhất có thể. Dễ thấy, muốn đáp ứng điều này, chúng ta nên nhét những class muốn được reuse cùng nhau vào chung một package, càng nhiều càng tốt. Lý tưởng là package đầy ắp các class muốn reuse cùng nhau.
Chúng ta có thể thấy nguyên lý Reuse/Release Equivalence và Common Reuse liên quan với nhau rất chặt chẽ. Thỏa mãn được nguyên lý này sẽ thường kéo theo thỏa mãn nguyên lý kia.
Bạn có thể hỏi: “làm sao biết trước cái người đó là ai và anh ta muốn reuse các class nào?” Good question! Và câu trả lời là kinh nghiệm! Chỉ có kinh nghiệm mới giúp chúng ta lường trước được những kiểu cách reuse mà thôi.
The classes in a component should be closed together against the same kinds of changes. A change that affects a component affects all the classes in that component and no other components.
Bắt đầu từ nguyên lý này, chúng ta sẽ không nói về reuse nữa, mà sẽ nói về sự thay đổi. Một lần nữa, kinh nghiệm có vai trò tuyệt đối ở đây bởi chúng ta cần phải đoán trước những kiểu thay đổi có thể xảy ra. Đoán được chúng rồi, ta sẽ thiết kế package để khi có thay đổi xảy ra, thì số lượng package bị thay đổi là ít nhất có thể. Lý tưởng là một package cho mỗi một sự thay đổi.
Muốn làm được như thế, thì package cần phải Single Responsibility, như class vậy.
Allow no cycles in the component dependency graph.
Nguyên lý này là hiển nhiên và tương tự như với class. Để xuất hiện một chu trình của những sự phụ thuộc là điều tối kị, vì một thay đổi ở một package sẽ kéo theo tất cả các package khác thuộc chu trình bị thay đổi theo.
Một package hay biến động thì nên hạn chế để package khác phụ thuộc vào nó. Nói cách khác, các package nên phụ thuộc theo chiều hướng từ biến động tới ổn định.
A component should be as abstract as it is stable.
Nguyên lý này thực chất là bổ trợ thêm cho nguyên lý 5. Một package ổn định thì nên có tính trừu tượng cao (chứa nhiều abstract class). Ngược lại, một package hay biến động thì không nên trừu tượng để có thể dễ thay đổi.
VỚI 6 NGUYÊN LÝ NÀY, HÃY CÙNG ĐIỂM QUA 3 HƯỚNG TIẾP CẬN DƯỚI ĐÂY CỦA SIMON BROWN, TÁC GIẢ CỦA C4 MODEL VỀ CÁCH ĐÓNG GÓI PACKAGE CHIA THEO LAYER, FEATURE VÀ COMPONENT TRONG BÀI VIẾT CỦA ÔNG CÓ NHAN ĐỀ “PACKAGE BY COMPONENT AND ARCHITECTURALLY-ALIGNED TESTING“
Tôi đã nghe và đã có rất nhiều cuộc thảo luận về “package by layer” và “package by feature” trong vài tuần qua. Cả hai đều có những lợi ích của chúng nhưng có một cách tiếp cận lai mà bây giờ tôi sẽ nói đến gọi là “package by component”.
PACKAGE BY LAYER
Giả sử rằng chúng ta đang xây dựng một ứng dụng web dựa trên Web-MVC pattern. Đóng gói codebase theo layer thường là cách tiếp cận mặc định bởi vì sau khi tất cả những gì trong sách, tutorial và framework mẫu cho chúng ta biết. Ở đây chúng tôi đang tổ chức mã bằng cách nhóm những thứ cùng loại với nhau.
Có 3 layer được đóng gói theo package đó là Controller ở trên cùng, dưới nó là Service layer, và cuối cùng là Data layer. Layer là cách gom nhóm codebase theo chức năng kỹ thuật. Các thuật ngữ như “tách biệt các mối quan tâm (separation of concerns)” được đưa ra xung quanh để biện minh cho cách tiếp cận này và kiến trúc phân lớp thường được coi là “good thing”. Nếu cần phải thay đổi cơ chế truy cập dữ liệu? Không có vấn đề, mọi thứ đều ở cùng một nơi. Mỗi layer có thể được kiểm thử riêng biệt với những thành phần khác khác xung quanh nó, sử dụng các kỹ thuật mocking thích hợp, v.v. Vấn đề với các kiến trúc phân lớp là chúng thường biến thành một đống bùn lớn (big ball of mud), bởi vì trong Java, bạn tạo các public class cho phép truy cập công khai từ bên ngoài.
PACKAGE BY FEATURE
Thay vì tổ chức mã theo chiều ngang (horizontal slicing), package by feature có cách làm ngược lại bằng cách tổ chức mã theo chiều thẳng đứng (vertical slicing).
Bây giờ mọi thứ liên quan đến một tính năng duy nhất (hoặc bộ tính năng) nằm ở một nơi. Bạn vẫn có thể có một kiến trúc phân lớp, nhưng các layer nằm bên trong các gói tính năng. Nói cách khác, phân lớp là cơ chế tổ chức thứ cấp. Lợi ích thường được trích dẫn là “dễ dàng điều hướng codebase khi bạn muốn thực hiện thay đổi đối với một tính năng”, nhưng đây là điều nhỏ nhoi nhờ sức mạnh của các IDE hiện đại.
Bây giờ chúng ta có thể ẩn đi các class cụ thể bên trong và giữ cho chúng ra khỏi tầm nhìn từ phần còn lại của codebase. Câu hỏi là điều gì sẽ xảy ra khi tính năng mới C cần truy cập dữ liệu từ các tính năng A và B? Một lần nữa, trong Java, bạn sẽ cần bắt đầu tạo các public class cho phép truy cập công khai từ bên ngoài các gói và quả bong bóng lớn sẽ lại xuất hiện.
PACKAGE BY COMPONENT
Đây là một hướng tiếp cận lai với mục tiêu chính là làm tăng tính module hóa và phong cách mã hóa rõ nét về mặt kiến trúc.
Các tiền đề cơ bản ở đây là tôi muốn codebase của tôi được tạo thành từ một số coarse-grained (phức hợp) components, với một số presentation layer (giao diện web, giao diện người dùng trên máy tính để bàn, API, ứng dụng độc lập, v.v …) được xây dựng phía trên cùng của hệ thống. Một “component” theo nghĩa này là sự kết hợp của business logic và data access liên quan đến một điều cụ thể (ví dụ domain concept, bounded context, v.v …). Như đã mô tả trước đây, ứng với mỗi component, tôi tạo ra các public interface ra bên ngoài và che giấu các thể hiện detail bên trong package, ví dụ như là data access code. Nếu tập tính năng mới C cần truy cập dữ liệu liên quan đến A và B, nó sẽ bị buộc phải sử dụng các public interface của các component A và B. Không được phép truy cập trực tiếp vào lớp detail bên trong và bạn có thể thực hiện điều này bằng cách sử dụng access modifiers của Java đúng cách. Một lần nữa, “kiến trúc phân lớp” là một cơ chế tổ chức thứ cấp. Để làm việc này, bạn phải dừng sử dụng từ khoá public một cách mặc định. Cấu trúc này đưa ra một số câu hỏi thú vị về kiểm tra, không chỉ về cách chúng tôi mock các đoạn mã data access để tạo ra các “unit test” nhanh.
ARCHITECTURALLY-ALIGNED TESTING
Câu trả lời ngắn gọn là đừng bận tâm, trừ khi bạn thực sự cần. Tôi đã nói về và viết về điều này trước đây, nhưng architecture và testing có liên quan với nhau. Thay vì là các mô hình kiểm thử điển hình (unit test thì rất nhiều, integration test thì ít hơn và thậm chí các UI test cũng ít), hãy xem xét điều dưới đây.
Tôi đang tránh để không sử dụng thuật ngữ “Unit testing” bởi vì mỗi người đều có cái nhìn khác nhau về độ lớn của một unit. Thay vào đó, tôi đã thông qua một chiến lược mà một số class có thể và nên được kiểm thử một cách riêng biệt. Điều này bao gồm những thứ như lớp domain classes, utility classes, web controllers (sử dụng mocked components), v.v … Sau đó, có thể dễ dàng để test các component thông qua các public interface. Nếu tôi có một component lưu trữ dữ liệu trong một cơ sở dữ liệu MySQL, tôi muốn kiểm thử tất cả mọi thứ từ public interface ngay trở vô cơ sở dữ liệu MySQL. Đây thường được gọi là “integration test”, nhưng một lần nữa, thuật ngữ này có nghĩa khác nhau đối với những người khác nhau. Tất nhiên, coi các component như là một hộp đen là dễ dàng hơn nếu tôi có quyền kiểm soát mọi thứ bên trong nó. Nếu bạn có một component đang gửi tin nhắn không đồng bộ hoặc sử dụng dịch vụ bên thứ ba, có thể bạn sẽ cần phải thêm các điểm dependency injection (ví dụ: ports and adapters) để kiểm tra component này một cách đầy đủ, nhưng hãy coi đây là ngoại lệ. Tất cả điều này vẫn còn áp dụng nếu bạn đang xây dựng hệ thống theo hướng microservice. Bạn có lẽ sẽ có một số bài kiểm tra cấp thấp, hy vọng một loạt các bài kiểm thử dịch vụ nơi bạn đang thử nghiệm các dịch vụ microservices thông public interface của chúng, và một số thử nghiệm hệ thống chạy các kịch bản từ đầu đến cuối. Oh, và bạn vẫn có thể viết tất cả điều này theo phong cách test-frst, TDD nếu đó là cách bạn làm việc.
Tôi đang sử dụng chiến lược này cho một số hệ thống mà tôi đang xây dựng và dường như nó hoạt động tốt. Tôi có một codebase tương đối đơn giản, sạch sẽ và trung thực với những dependency dễ hiểu, dễ maintain. Chiến lược này cũng làm cầu nối khoảng cách giữa mô hình, trong đó codebase thực sự phản ánh ý định của kiến trúc. Nói cách khác, chúng ta thường vẽ các components trên bảng khi thảo luận về kiến trúc, nhưng những component này khó tìm thấy trong codebase. Packaging code by layer là một lý do chính tại sao không phù hợp giữa thiết kế và codebase. Những người quen thuộc với mô hình C4 của tôi có lẽ sẽ nhận thấy việc sử dụng thuật ngữ “class” và “component”. Đây không phải là sự trùng hợp ngẫu nhiên. Kiến trúc và kiểm có liên quan nhiều hơn chúng ta đã thừa nhận trong quá khứ.
Đây là bài viết trong loạt bài viết về “Tổng quan về sự phát triển của kiến trúc phần mềm“. Đây là loạt bài viết chủ yếu giới thiệu về một số mô hình kiến trúc phần mềm hay nói đúng hơn là sự phát triển của chúng qua từng giai đoạn, qua đó giúp chúng ta có cái nhìn tổng quát, up-to-date và là roadmap để bắt đầu hành trình chinh phục (đào sâu) thế giới của những bản thiết kế với vai trò là những kỹ sư và kiến trúc sư phần mềm đam mê với nghề.
Lời đầu tiên, image câu view ngoài bài viết có liên quan mật thiết tới nội dung bài viết. Ngoài mục đích giúp anh em đọc bài đỡ chán, nhìn lâu vào hình có thể ngộ được cái vẻ đẹp của Iterator Boobs, lộn Iterator Pattern.
Note: Anh em đọc đến cuối bài mà chưa hiểu gì, thì cứ kéo lên nhìn hình cô em xinh đẹp, nhìn ngắm một hồi rồi đọc lại là sẽ hiểu. =)))
2. Tại sao tao cần biết về cái Pattern củ l*z này?
Xin thưa là do cái Iterator Pattern này nằm trong nhóm các pattern cơ bản, cực kì hữu dụng khi hiểu về nó.
Đầu tiên, ta có rất nhiều cách để lưu trữ một collection các objects. Kiểu sử dụng có thể là: List, Dictionaries và Sets.
Ví dụ, lưu trữ một array đồ uống có thể thực hiện như sau:
// Java
// Lưu trữ danh sách menu
String[] menuItems = new String[MAX_ITEMS];
menuItem[0] = "Bạc sỉu nóng nhiều đá";
menuItem[1] = "Đen đá không lạnh";
Ngoài ra, Java cũng cung cấp ArrayList, lưu trữ mảng Object.
// Java
// Lưu trữ danh sách menu
ArrayList<String> menuItems = new ArrayList<String>();
menuItem.add("Bạc sỉu nóng nhiều đá");
menuItem.add("Đen đá không lạnh");
Ngon lành cành đào. Tiếp theo, ta cần hiện thực một method in ra tất cả những item trong menu đồ uống. Một đoạn code bình thường sẽ là:
// Java
// In danh sách các item trong menu
for (int i; i < items.length; i++) {
String item = items[i];
// Print the item
}
Đệt, lỡ thằng nào dở chứng. Nó không lưu menu trong cái array mà chuyển qua ArrayList? -> Khá đau thương nhưng phải sửa code:
// Java
// In danh sách các item trong menu
// Đm thằng shrimp head nào rảnh đổi kiểu từ array sang ArrayList nhé!.
for (int i; i < items.size; i++) {
String item = items.get(i);
// Print the item
}
Chà, đây chính là lý do Iterator Pattern ra đời.
Iterator Pattern cung cấp một thiết kế (pattern) cho phép hoạt động trên nhiều loại Collections. Trường hợp input đầu vào là Set -> work, trường hợp là Vector -> work nốt. Chà, đấy chính là lý do cái Iterator Pattern này ra đời.
3. Hiện thực dư lào?
Đầu tiên, Iterator:
The Iterator interface declares the operations required for traversing a collection: fetching the next element, retrieving the current position, restarting iteration, etc
Iterator interface khái báo các thao tác cần thiết để duyệt qua Collection: Tìm nạp phần tử kế tiếp, nhận về vị trí hiện tại, khởi động lại iteration, …
// Java
// Khởi tạo interface Iterator
// Interator.java
public interface Iterator {
}
// Java
// Khởi tạo interface Iterator
// Container.java
public interface Container {
public Iterator getIterator();
}
// Java
// Class menu implement Container
public class Menu implements Container {
public String menu[] = {"Cà phê" , "Nước cam" ,"Bò Húc" , "Sting"};
@Overrides
public Iterator getIterator() {
return new MenuIterator();
}
private class MenuIterator implements Iterator {
int index;
// Return boolean.
// True - Collection còn phần tử
// False - Cuối collection
@Override
public boolean hasNext() {
if(index < menu.length){
return true;
}
return false;
}
// Return object
// Trả về phần tử kế tiếp
@Override
public Object next() {
if(this.hasNext()){
return menu[index++];
}
return null;
}
}
}
// Java
// Call interator and print item in menu
public class IteratorPatternMenu {
public static void main(String[] args) {
MenuIterator menuIterator = new MenuIterator();
for(Iterator iter = menuIterator.getIterator(); iter.hasNext();){
String menuItem = (String)iter.next();
System.out.println("Menu item : " + menuItem);
}
}
}
4. Sử dụng khi nào?
Use the Iterator Pattern when you want your code to be able to traverse different data structures or when types of these structures are unknown beforehand.
Sử dụng Iterator khi chúng ta muốn của mình sẵn sàng cho các kiểu dữ liệu khác nhau HOẶC khi kiểu dữ liệu đầu vào không được biết trước.
Use the Iterator Pattern when your collection has a complex data structure under the hood, but you want to hide its complexity from clients (either for convenience or security reasons).
Sử dụng Interator pattern khi collection của chúng ta có kiểu dữ liệu phức tạp. Nhưng ta lại muốn giấu sự phức tạp đó (không có client biết). Có thể là vì lý do thuận tiện hoặc bảo mật).
Biết – nhưng không phải lúc nào cũng dùng đâu nhé =))).
5. Điểm lợi, điểm hại của Iterator Pattern
[LỢI]: Open/Closed Principle – Sử dụng Iterator giúp ta có thể implement các kiểu collections và interator khác nhau. Đưa chúng vào các lớp đã triển khai. Hạn chế sửa code.
[LỢI]: Sử dụng Iterator Pattern giúp ta iterate các collection giống nhau theo hình thức đa luồng. Mỗi collection sẽ có một object iterator riêng của nó.
[HẠI]: Sẽ là hơi QUÁ CMN ĐÀ nếu ta chỉ có một collection đơn giản nhưng lại triển khai iterator pattern. Dùng dao mổ trâu đi làm thịt chim sẻ. =))). Nên dùng đúng lúc.
[HẠI]: Sử dụng Iterator Pattern đôi khi làm giảm hiệu suất của việc lặp Collections.
6. Tham khảo
Anh em nào có hứng có thể tìm hiểu thêm về Strategy Pattern (một pattern khá hay).
Bài viết được sự cho phép của blogchiasekienthuc.com
Bạn có biết? Google Chrome hiện đang là trình duyệt web mã nguồn mở được sử dụng nhiều nhất trên thế giới hiện nay.
Theo Statista thì có khoảng 66% người sử dụng Internet trên toàn cầu sử dụng trình duyệt web này, đó là chưa kể đến các trình duyệt web sử dụng lõi Chromium nữa.
Có rất nhiều lý do để khiến nó trở thành trình duyệt web phổ biến nhất thế giới, như là: tốc độ tải trang nhanh, độ bảo mật cao, tính năng mới được bổ sung và update liên tục… và hơn hết, nó có một kho ứng dụng khổng lồ do nhiều nhà phát triển trên thế giới đóng góp.
Chúng được thiết kế để phục vụ cho những nhu cầu cụ thể của một cá nhân, hay một nhóm người, thậm chí là cả cộng đồng. Hiện tại, có đến hàng triệu ứng dụng hữu ích trên Chrome Web Store, cả miễn phí lẫn có phí.
Trong bài viết này, mình sẽ giới thiệu với các bạn một số Extension (tiện ích mở rộng) đang được sử dụng yêu thích và được khá nhiều người sử dụng hiện nay.
Nói tới tiện ích mở rộng thì không thể nào không nhắc tới LastPass, một trình quản lý mật khẩu phổ biến nhất thế giới, với hơn 10 triệu người đang sử dụng hàng ngày.
Tiện ích này hỗ trợ lưu trữ các tài khoản và mật khẩu trực tuyến của bạn, hỗ trợ đăng nhập vào các website mà bạn đã đăng ký. Bạn sẽ chỉ đăng ký hay đăng nhập một lần trên website, LastPass sẽ lưu trữ, tự động điền và đăng nhập vào lần sau.
Ngoài ra thì bạn cũng có thể sử dụng LastPass để tạo ra các mật khẩu ngẫu nhiên trong trường hợp bạn đăng ký tài khoản mới.
// Bài viết trên đã có link tải của tất cả các trình duyệt web và tất cả các hệ điều hành. Bạn xem hướng dẫn trong bài viết đó nha.
#2. Pushbullet
Link tải tiện ích Pushbullet: Link tải tại đây nha các bạn (hỗ trợ Android, Google Chrome, FireFox và Windows)
Đây là một tiện ích còn khá mới, tuy nhiên nó cũng đang được rất nhiều người trên thế giới sử dụng (cụ thể là gần 1 triệu người).
Tính năng chính của Pushbullet là hỗ trợ người dùng gửi thông báo từ điện thoại của bạn sang điện thoại khác, hoặc đẩy thẳng lên trên máy tính PC của bạn.
Ngoài ra, Pushbullet còn có thể gửi link từ máy tính sang smartphone, tự động hóa công việc với Pushbullet và IFTTT, tạo danh sách các công việc cần làm,… và nhiều thứ hay ho khác. Bạn hãy khám phá nhé.
#3. CyberGhost VPN Free Proxy
Bạn có thể vào đây để tải về và sử dụng trên nhiều thiết bị khác nhau (Android, iOS, Windows, MacOS, Linux, Chrome, FireFox….)
CyberGhost là nhà cung cấp VPN đáng tin cậy, với hơn 15 triệu khách hàng trên toàn thế giới.
CyberGhost VPN Free Proxy là một tiện ích mở rộng hỗ trợ người dùng truy cập Internet an toàn hơn với lưu lượng băng thông không giới hạn, và 100% MIỄN PHÍ (tất nhiên là có cả bản trả phí)!
CyberGhost sẽ mã hóa lưu lượng truy cập trên trình duyệt của bạn để bạn để có thể chắc chắn rằng không có kẻ nào có thể theo dõi, và biết được vị trí mà bạn đã truy cập web.
Nói chung là nếu bạn đã biết về Fake IP rồi thì cũng không có gì phải giải thích nhiều cả, đơn giản là nó sẽ giúp bạn ẩn địa chỉ IP khi lướt web, từ đó bạn sẽ được ẩn danh và lướt web an toàn hơn.
CyberGhost sẽ giúp bạn Fake địa chỉ IP sang nhiều Quốc gia khác nhau và cho ra tốc độ tương đối ổn định. Nói chung là tiện ích này được đánh giá rất cao bởi người dùng.
#4. Save to Pocket
Trang chủ: getpocket.com
Link cài đặt tiện ích tại đây nhé các bạn !
Nếu bạn là một sinh viên hay là một nghiên cứu sinh đang tìm kiếm tài liệu tham khảo cho dự án của mình thì có lẽ Save to Pocket sẽ là một trợ thủ đắc lực dành cho bạn đấy.
Save to Pocket không giúp bạn tìm kiếm tài liệu được đâu, nhưng nó sẽ giúp bạn lưu trữ lại các bài viết đó để bạn có thể đọc khi cần, kể cả khi máy tính của bạn không có kết nối Internet.
Tiện ích này hỗ trợ cả ứng dụng trên điện thoại nữa, vậy nên bạn có thể đồng bộ dữ liệu (các tài liệu mà bạn đã lưu bằng tiện ích này) qua lại giữa máy tính và điện thoại một cách nhanh chóng.
Việc mà bạn cần làm tạo một tài khoản, đăng nhập và sử dụng. Để lưu nội dung, bạn nhấn vào biểu tượng Save to Pocker => biểu tượng màu đỏ chứng tỏ…đã lưu 😁
Nói chung là cách sử dụng vo cùng đơn giản thôi, trong bài viết này mình chủ yếu giới thiệu là chính nên mình sẽ không đi vào hướng dân chi tiết cho từng tiện ích nhé các bạn.
#5. Kết luận
Nói thật là trên kho ứng dụng của Google nó khổng lồ quá. Mình chỉ chắc một điều là còn rất nhiều tiện ích mở rộng với những tính năng tuyệt vời khác..
Nhưng trong phạm vi của bài viết này, mình chỉ xin giới thiệu đến các bạn 4 tiện ích mở rộng mà mình đang sử dụng mà thôi. Trong quá trình sử dụng, mình sẽ tổng hợp và viết bài chia sẻ thêm nhé.
Còn bạn, bạn đang sử dụng tiện ích mở rộng nào trên Google Chrome để phục vụ cho công việc của bạn? hãy chia sẻ nó ở bên dưới phần bình luận để a/e tham khảo nhé !
Bài viết được sự cho phép của tác giả Edward Thien Hoang
Mục tiêu của thiết kế làm làm sao để hệ thống xây dựng lên nhanh chóng, dễ dàng thích nghi với các thay đổi về nghiệp vụ và công nghệ, dễ dàng bảo trì về sau. Để làm được điều đó, thiết kế cần đảm bảo tính low coupling, high cohesion và encapsulation. Tuy nhiên trong quá trình phát triển, làm sao để biết được là chúng ta đã đi đúng hướng, các class, component, … tạo ra đã đáp ứng được yêu cầu về mặt thiết kế hay chưa? Vì vậy chúng ta cần một số công cụ, phương thức nhằm đo lường (measure) các số liệu (metric) về thiết kế trong suốt quá trình thiết kế và phát triển để đảm bảo mọi thứ đang được đi đúng hướng.
Metric này được sử dụng để đo mối tương quan giữa các component. Theo định nghĩa, nó là một số class trong một component phụ thuộc vào các class trong các component khác, trả lời cho câu hỏi mật độ một component phải thay đổi khi có sự thay đổi ở các component khác như thế nào.
Hình 1 – Outgoing dependencies
Trong Hình 1 cho thấy rằng component A có sự phụ thuộc đến (outgoing dependency) 3 component khác, đó là lý do tại sao chỉ số Ce cho component này là 3.
Khi chỉ số Ce > 20 cho thấy sự không ổn định của một component, thay đổi trong bất kỳ component bên ngoài có thể gây ra sự cần thiết phải thay đổi bên trong component này. Chỉ số Ce nên nằm trong khoảng từ 0 đến 20, giá trị cao hơn gây ra vấn đề với việc phát triển và bảo trì.
AFFERENT COUPLING (CA)
Metric này là phần bổ sung cho Metric Ce và được sử dụng để đo lường một loại phụ thuộc khác giữa các component. Nó cho phép chúng ta đo độ nhạy của các component còn lại với những thay đổi trong component phân tích, trả lời cho câu hỏi liệu những thay đổi trong component này sẽ ảnh hưởng đến những chỗ khác như thế nào.
Hình 2 – Incoming dependencies
Trong Hình 2 có thể thấy rằng component A chỉ có 1 sự phụ thuộc vào (incoming dependency) component X, đó là lý do tại sao giá trị của chỉ số Ca bằng 1.
Giá trị của Ca càng cao nghĩa là độ ổn định (stability) của component sẽ cao. Điều này cho thấy có rất nhiều component đang depend trên component này. Vì vậy, nó không thể được sửa đổi đáng kể bởi vì xác suất lây lan những thay đổi như vậy sẽ tăng lên.
Giá trị khuyến nghị của Ca vào khoảng 0 đến 500.
INSTABILITY (I)
Metric này được sử dụng để đo độ nhạy tương đối của component với những thay đổi. Theo công thức dưới đây:
Trong đó: Ce – phụ thuộc đến, Ca – phụ thuộc vào
Hình 3 – Instability
Trong Hình 3 có thể thấy rằng component A phụ thuộc đến 3 component và có 1 component phụ thuộc vào nó, do đó theo công thức ta có I = 0,75.
Trên cơ sở giá trị của chỉ số I chúng ta có thể phân biệt hai loại thành phần:
Những component có nhiều phụ thuộc đến và không nhiều phụ thuộc vào (giá trị I tiến gần tới 1), điều này không ổn định bởi vì các component này có khả năng bị thay đổi rất cao;
Những component có nhiều phụ thuộc vào và không nhiều phụ thuộc đến (giá trị I gần về 0), do đó chúng sẽ có khả năng ít bị thay đổi, tuy nhiên, sự thay đổi từ chúng sẽ có ảnh hưởng rất lớn.
Các giá trị ưu tiên cho chỉ số I phải nằm trong phạm vi từ 0 đến 0.3 hoặc 0.7 đến 1. Các component phải rất ổn định hoặc rất không ổn định, và nên tránh các component có độ ổn định trung bình.
ABSTRACTNESS (A)
Metric này được sử dụng để đo mức độ trừu tượng của component được tính theo công thức:
Trong đó: Tabstract là số lớp trừu tượng, interface trong một component, T concrete là số lớp cụ thể trong một component
Các giá trị khuyến nghị cho chỉ số A phải có các giá trị cực trị gần 0 hoặc 1. Các component có độ ổn định (I gần bằng 0), nghĩa là chúng phụ thuộc ở mức rất thấp trên các component khác, cũng nên được trừu tượng (chỉ số A cũng gần bằng 1). Ngược lại, các component không ổn định (I gần với 1) nên bao gồm các lớp cụ thể (A cũng gần bằng 0).
Thêm vào đó, cần lưu ý rằng việc kết hợp tính trừu tượng và tính ổn định cho phép Martin hình thành luận văn về sự tồn tại của luồng chính (Main sequence) (Hình 4).
Hình 4 – Main sequence
Trong trường hợp tối ưu, tính không ổn định của lớp được bù đắp bởi tính trừu tượng của nó, có một phương trình I + A = 1. Các class được thiết kế tốt nên tập trung xung quanh điểm kết thúc của đồ thị này theo luồng chính.
Trong đó: A- tính trừu tượng, I – tính bất ổn
Giá trị của chỉ số D có thể được giải thích theo cách sau: nếu chúng ta đặt một lớp cho một đồ thị của luồng chính (Hình 5) thì khoảng cách của nó từ luồng chính sẽ tỉ lệ thuận với giá trị của D.
Hình 5 – Khoảng cách được chuẩn hóa từ luồng chính
Giá trị của D phải càng thấp càng tốt để các thành phần được đặt gần với luồng chính.
Ngoài ra, hai trường hợp cực kỳ bất lợi được xem xét:
A = 0 và I = 0, một component rất ổn định (stable) và cụ thể (concrete), tình huống không được mong muốn bởi vì chúng rất khó hoặc không thể mở rộng;
A = 1 và I = 1, hoàn toàn không thể vì một component hoàn toàn trừu tượng phải có một số kết nối với bên ngoài, do đó có thể tạo ra cá instance để implement các chức năng được định nghĩa trong các lớp trừu tượng trong component này.
SUMMARY
Sử dụng các số liệu của Martin, chúng ta có thể dễ dàng đánh giá các mối quan hệ giữa các component trong dự án và xác định liệu có cần phải thực hiện các thay đổi để tránh những vấn đề có thể xảy ra trong tương lai hay không. Tất cả các số liệu được thảo luận trong bài viết này có thể được đo cho các dự án trong .NET sử dụng công cụ NDepend hoặc JDepend đối với Java.
Đây là bài viết trong loạt bài viết về “Tổng quan về sự phát triển của kiến trúc phần mềm“. Đây là loạt bài viết chủ yếu giới thiệu về một số mô hình kiến trúc phần mềm hay nói đúng hơn là sự phát triển của chúng qua từng giai đoạn, qua đó giúp chúng ta có cái nhìn tổng quát, up-to-date và là roadmap để bắt đầu hành trình chinh phục (đào sâu) thế giới của những bản thiết kế với vai trò là những kỹ sư và kiến trúc sư phần mềm đam mê với nghề.
Bài viết được sự cho phép của tác giả Kien Dang Chung
URI là một khái niệm tổng quát, có trước cả URL tuy nhiên do thực tế việc sử dụng URL nhiều hơn nên mọi người thường biết đến URL nhiều hơn. Vậy chính xác URI là gì? nó có quan hệ như thế nào với URL, chúng ta cùng tìm hiểu.
URI là viết tắt của Uniform Resource Identifier – Nhận diện tài nguyên thống nhất, URI là một chuỗi ký tự được sử dụng để nhận diện các tài nguyên, chuỗi ký tự này có thể là một định vị tài nguyên hoặc một định danh tài nguyên hoặc cả hai. Tài nguyên ở đây là tài nguyên mạng như các siêu văn bản, file hình ảnh, file âm thanh, file video… Với định nghĩa ở trên, các chuỗi sau đây là URI:
Với một số URI, trình duyệt không thể yêu cầu nội dung trực tiếp, tuy nhiên nó vẫn là nhận diện một tài nguyên duy nhất và có thể có những ứng dụng máy chủ tạo ra dịch vụ để chuyển nó thành nội dung mà trình duyệt có thể yêu cầu được.
Cấu trúc URI
Cấu tạo của một URI như sau:
URI = scheme:[//authority]path[?query][#fragment]
Trong đó:
scheme: chính là giao thức mạng sử dụng để truyền dẫn dữ liệu, các giao thức phổ biến mà chúng ta biết là http, https, ftp, mailto, irc…, phân cách giao thức với phần còn lại bằng ký tự :.
authority là phần tổ hợp bao gồm các phần nhỏ hơn
authority = [userinfo@]domain[:port]
userinfo: Thông tin người dùng bao gồm tên đăng nhập và mật khẩu, chỉ sử dụng với các URL được bảo mật cần đăng nhập.
domain: tên miền của website là ánh xạ 1 – 1 từ một tên có thể nhớ sang địa chỉ IP của máy chủ web nơi chứa nội dung trang web.
port: Số cổng sử dụng bởi giao thức trên máy chủ.
path: Đường dẫn đến nội dung trang web, đường dẫn này là đường dẫn trong nội bộ website, phân cách giữa thư mục cha và thư mục con bởi dấu gạch chéo (/).
query: là chuỗi truy vấn, chứa các thông tin theo cặp tên/giá trị được gửi đến máy chủ web, mỗi cặp này cách nhau bởi dấu &.
fragment: là các chỉ mục con của nội dung, được bắt đầu với dấu #.
Nhiều ứng viên nghĩ rằng chỉ cần chuẩn bị thật tốt trước và trong buổi phỏng vấn là đã hoàn thành xong nhiệm vụ và ngồi chờ đợi kết quả. Nhưng có vẻ như điều này không hẳn đúng. Sau khi đã hoàn thành buổi phỏng vấn, bạn sẽ còn cần thao tác thêm một số việc khác nếu muốn đạt được kết quả hoàn hảo nhất. Vậy sau khi phỏng vấn cần làm gì để nhà tuyển dụng đánh giá cao hơn sự chuyên nghiệp của ứng viên? Cùng tìm hiểu thêm một số thông tin hữu ích với bài viết dưới đây.
Viết thư cảm ơn nhà tuyển dụng và người phỏng vấn
Sau khi kết thúc một buổi phỏng vấn, việc gửi thư cảm ơn đến nhà tuyển dụng được đánh giá là một trong những hành động cần thiết của câu hỏi sau khi phỏng vấn cần làm gì. Thư cảm ơn không chỉ giúp bạn trông chuyên nghiệp hơn mà còn cho nhà tuyển dụng thấy được mong muốn thật sự có được công việc này của ứng viên. Nhờ thế, nhà tuyển dụng có thể để tâm hơn đến hồ sơ tuyển dụng của bạn cũng như tăng cơ hội trúng tuyển vào vị trí ứng tuyển hơn.
Hãy nhớ rằng, thái độ luôn quan trọng hơn trình độ. Có đôi khi việc bạn đậu một kỳ phỏng vấn phần nhiều lại nhờ vào thái độ cầu tiến và sẵn sàng lắng nghe mà bạn thể hiện với đối phương. Thời gian hợp lí nhất để gửi thư cảm ơn nhà tuyển dụng là trong vòng 2 ngày sau buổi phỏng vấn. Nội dung mail đơn giản chỉ là cảm ơn công ty đã dành thời gian phỏng vấn và trao đổi thông tin với bạn. Ngắn gọn, xúc tích và cho thấy mong muốn thật sự của bạn là đủ.
Không phải ai cũng thành công khi phỏng vấn ở bất kì công ty nào. Mỗi công ty đều có những tiêu chí lựa chọn nhân tài khác nhau và những gì bạn nhìn thấy chỉ là các tiêu chí chung. Cuộc phỏng vấn sẽ giúp nhà tuyển dụng đánh giá một cách chính xác sự phù hợp của bạn với văn hóa công ty. Do đó, đừng quá đặt nặng vấn đề phải thông qua buổi phỏng vấn để tránh thất vọng khi không đạt được kết quả như ý muốn.
Bạn không đậu phỏng vấn không đồng nghĩa với việc bạn không đủ năng lực hay còn yếu kém. Buồn bã và thất vọng có thể khiến bạn đánh mất cơ hội. Vẫn còn rất nhiều công việc khác đang chờ đợi bạn do đó đừng quá căng thẳng hay mất niềm tin vào bản thân. Hãy chuẩn bị sẵn sàng tâm lí cho trường hợp xấu nhất là bạn không thể đậu phỏng vấn.
Đánh giá lại một cách tổng quan về buổi phỏng vấn và tìm kiếm những cơ hội tiếp theo nếu không đạt
Dù kết quả đạt hay không đạt, quá trình phỏng vấn có suôn sẻ hay không, bạn vẫn nên dành thời gian để đánh giá lại một cách tổng quan buổi phỏng vấn của mình. Bạn đã trả lời những câu hỏi nào, bạn trả lời các câu hỏi ấy ra sao, cá nhân bạn đã cảm thấy mình làm tốt trong các câu trả lời chưa và nên cải thiện câu trả lời như thế nào để gây ấn tượng với nhà tuyển dụng…
Đặc biệt với những trường hợp phỏng vấn không thành công, ứng viên càng nên dành nhiều thời gian cho việc sắp xếp lại thông tin và các vấn đề mình mắc phải trong buổi phỏng vấn. Việc đánh giá lại buổi phỏng vấn chắc chắn sẽ giúp ích rất nhiều và giúp bạn làm tốt hơn cho những cuộc phỏng vấn khác.
Thị trường công việc luôn dồi dào, sẽ chẳng bao giờ khan hiếm công việc để ứng viên tìm kiếm cơ hội mới cho mình. Quan trọng là bạn có biết cách khai thác các thông tin và khai phá năng lực của chính mình hay không. Sau mỗi cuộc phỏng vấn không thành công chắc chắn bạn đều sẽ học được những bài học ý nghĩa và hữu ích cho sau này. Chính vì thế, hãy tận dụng mọi nguồn thông tin đã được tiếp thu để tìm kiếm những cơ hội mới cho chính mình.
Hãy cố gắng áp dụng những kinh nghiệm bạn đã đúc kết được từ những cuộc phỏng vấn trước trong các buổi phỏng vấn sau này để cải thiện kết quả. Chắc chắn bạn sẽ sớm có được công việc mơ ước của mình và đạt được vị trí mình hằng mong muốn.
Bạn có thể không thể hiện thật sự xuất sắc trong quá trình phỏng vấn nhưng những hành động chuyên nghiệp sau buổi phỏng vấn cũng giúp bạn ghi điểm nhiều hơn trong mắt nhà tuyển dụng. Đây cũng là cách hay ho giúp bạn hoàn thiện những kỹ năng mềm rất hữu ích cho công việc của mình sau này. Theo dõi TopDev và đón đọc thêm nhiều bài viết bổ ích khác về những kinh nghiệm cũng như kỹ năng làm việc tốt hơn cho quá trình làm việc của bạn sau này.
Bài viết được sự cho phép của blogchiasekienthuc.com
Windows 11 thực sự đang rất hót trong thời gian gần đây, cộng đồng những người yêu công nghệ, đặc biệt là hội những người có niềm đam mê với máy tính đều rất mong ngóng sự ra đời chính thức của hệ điều hành này.
Mình nghĩ là Windows 11 sẽ được chào đón hơn so với các phiên bản trước đó bởi giao diện của nó giờ đã quá đẹp rồi, và nó lại còn mang hơi hướng, cũng như kế thừa một số chức năng rất hay có trên Windows 7 trước đây.
Còn rất nhiều điều để phân tích về hệ điều hành Windows 11 này, và ở trong bài viết ngày hôm nay mình sẽ tổng hợp lại những câu hỏi thường gặp về Windows 11, mục đích clà để giúp các bạn có thể hiểu kỹ hơn về hệ điều hành này.
#1. Sự khác biệt giữa Windows 10 và Windows 11 là gì?
Ngoài việc thừa hưởng toàn bộ tính năng, và tính bảo mật hiện có trên Windows 10 thì Windows 11 còn yêu cầu mức độ bảo mật cao hơn nữa thông qua chip bảo mật TPM.
Giao diện và các icon được thiết kế lại đẹp hơn và “xịn” hơn. Kèm theo đó là các công cụ, âm thanh và ứng dụng mới. Tất cả đã được kết hợp với nhau để tạo ra một hệ điều hành Windows 11 hoàn hảo, đem lại những trải nghiệm mới mẻ hơn.
#2. Khi nào thì tôi có thể mua máy tính cài đặt sẵn Windows 11?
Microsoft dự tính vào cuối năm nay, tức năm 2021 !
#3. Máy tính của tôi có cài đặt hoặc nâng cấp được lên Windows 11 không?
Hầu hết các máy tính được sản xuất từ năm 2017 trở lại đây đều có thể nâng cấp được lên Windows 10, nhưng tất nhiên nó phải đạt được cấu hình ở mức tối thiểu.
#4. Khi nào thì tôi có thể nâng cấp Windows 10 lên Windows 11?
Kế hoạt triển khai nâng cấp diện rộng vẫn đang được Microsoft hoàn thiện, nhưng hầu hết các thiết bị sẽ có thể nâng cấp vào cuối năm 2021 và đầu năm 2022.
Tuy nhiên, không phải tất cả các máy tính đang chạy Windows 10 đều có thể nâng cấp lên Windows 11. Nó còn phụ thuộc vào phần cứng và TPM của máy.
#5. Nếu máy tính tôi không nâng cấp lên được Windows 11 thì sao đây?
Tất nhiên rồi, kệ bạn chứ sao ᵔᴥᵔ
Đùa vậy thôi, Microsoft cam kết hỗ trợ hệ điều hành Windows 10 đến hết ngày 14 tháng 10 năm 2025. Vậy nên bạn hãy cố gắng cày cuốc để đến lúc đó mua máy mới nhé 🙂
#6. Làm thế nào để biết Windows 11 đã có sẵn để thực hiện nâng cấp?
Theo như khuyến cáo của Microsoft thì các bạn hãy chịu khó truy cập blogchiasekienthuc.com mỗi ngày để theo dõi và cập nhật các tin tức hay.
Ngoài ra thì bạn có thể vào phần Windows Update bằng cách: Nhấn tổ hợp phím Windows + I => chọn Update & Security để check UPDATE nha các bạn.
Thông tin thêm: Các bản nâng cấp lên Windows 11 sẽ được Microsoft phát hành vào cuối năm 2021. Và Windows Update sẽ chỉ thông báo khi máy tính của bạn đủ điều kiện phần cứng để nâng cấp.
#7. Nâng cấp từ Windows 10 lên Windows 11 hết bao nhiêu tiền?
Nếu bạn đang sử dụng Windows 10 bản quyền thì bạn có thể nâng cấp lên Windows 11 hoàn toàn miễn phí !
Nhưng tất nhiên là chỉ những máy đang chạy phiên bản Windows 10 mới nhất và đáp ứng đủ các điều kiện về phần cứng thì mới có thể nâng cấp lên được.
#8. Nhiều người đang nhầm lẫn giữa NÂNG CẤP và CẬP NHẬT
Bản CẬP NHẬT là các bản vá lỗi và bảo mật, cũng như là thêm các tính năng mới trong các phiên bản Windows. Và nó được triển khai quanh năm.
Còn bản NÂNG CẤP có nghĩa là thay đổi phiên bản Windows.. Ví dụ như là từ Windows 10 lên Windows 11, hoặc bạn chuyển từ Windows 10 Home sang Windows 10 Pro chẳng hạn..
#9. Ngày kết thúc chương trình nâng cấp Windows 10 lên Windows 11 miễn phí?
Theo như Microsoft công bố thì không có ngày kết thúc, tức là bạn có thể nâng cấp lên lúc nào cũng được. Không bị giới hạn ngày nâng cấp như đợt Windows 7 lên Windows 10.
#10. Windows 11 có chiếm nhiều dung lượng ổ cứng hơn Windows 10 không?
Windows 10 và Windows 11 yêu cầu dung lượng ổ cứng sấp sỉ như nhau. Tuy nhiên, nếu bạn thực hiện nâng cấp thì bạn sẽ phải chuẩn bị một phân vùng ổ cứng có dung lượng lớn hơn 64GB.
Bởi khi nâng cấp thì Windows 11 sẽ tạo ra một bản backup hệ điều hành cũ, để nếu chẳng may quá trình nâng cấp lên Windows 11 bị lỗi, hoặc sau khi nâng cấp lên bản cảm thấy không thích thì có thể Restore về lại Windows 10 được.
Nói nôm na là Microsoft tạo cho bạn một đường lùi đấy 🙂
Và bản backup này sẽ được tự động xóa đi trong vòng 10 ngày. Nên nếu bạn có đắn đó suy nghĩ thì hãy nghĩ nhanh và quyết định luôn trong vòng 10 ngày đó nhé.
#11. Tôi có thể quay trở lại Windows 10 sau khi nâng cấp lên Windows 11 không?
Như mình đã nói ở trên, Windows 11 sẽ tạo ra một bản backup của hệ điều hành cũ. Vậy nên bạn sẽ có 10 ngày để quay trở về phiên bản Windows cũ trước đó (các tệp và dữ liệu sẽ được giữ nguyên mà không bị mất).
[….updatting….]
#12. Lời kết
Vâng, trên đây là những câu hỏi thường gặp nhất về Windows 11. Mình sẽ tiếp tục cập nhật thêm những câu hỏi hay khác và update tại bài viết này.
Chính vì thế, nếu bạn quan tâm đến hệ điều hành này thì hãy Bookmark lại bài viết để tiện hơn cho việc theo dõi nhé !
Quay trở lại với chuỗi bài viết về Behavioral Pattern, tiếp sau thành công của Iterator Pattern – Đôi điều thú vị, Kieblog xin trân trọng giới thiệu về Chain of Responsibility. Là một pattern phổ biến và được sử dụng khắp mọi nơi, không nên bỏ qua pattern này khi tìm hiểu thêm về Design Pattern.
Lý thuyết lúc nào cũng loằng ngoằng lèo nghèo. Nhưng đọc thì vẫn phải đọc, không thể nói qua loa là đã hiểu sâu về Design Pattern khi không có chút ghi nhớ nào về định nghĩa.
Chain of Responsibility is a behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.
Chain of Responsibility là Design pattern hành vi, cho phép xử lý request theo một chuỗi các handlers. Khi nhận được request, mỗi handler sẽ quyết định việc xử lý request và chuyển nó đi tiếp cho handler tiếp theo trong chuỗi.
Cái tên nói lên tất cả: Chain (chuỗi), Responsibility (trách nhiệm). Một chuỗi các xử lí có trách nhiệm khác nhau, độc lập với nhau. Nhớ tên cũng là một cách hay để nhớ về concept của Design Pattern.
Rồi đó, nhìn thì thấy mỗi chuỗi dài các handler rồi đó. Nhưng từ đâu lại có pattern này, đẻ ra pattern này có thể giải quyết vấn đề nào?.
Muốn biết thì phải đọc về bài toán thực tế!.
2. Căn nguyên vấn đề
Kỷ nguyên của thương mại điện tử, tưởng tượng các ông đang develop cái Ordering System cho một hệ thống lớn. Bài toán trong đó bao gồm:
Chỉ user sau khi authenticated mới được tạo order (sau khi login)
User nào được cấp quyền full (administrative permissions) có thể truy cập tới tất cả orders đang có.
Easy, một cái service có thể được viết để handle dăm ba cái chuyện Authentication, Authorzation,..
Tới lúc này, mọi thứ trông vẫn có vẻ ổn áp, điều hòa. Nhưng trong vài tháng, sẽ có một số vấn đề phát sinh:
Một đồng nghiệp vui tính nhắc là nếu gửi dữ liệu raw (raw data) thẳng tới server có thể phát sinh nhiều vấn đề về security. Ta cần viết thêm một phần mới (validation). Step này cũng được thêm vào services
Một đồng nghiệp khác không vui tính là mấy lại suggesst: Nếu không filter request, có thể server sẽ bị tấn công bởi brute force. password cracking. Sợ quá, lại viết thêm để check filter các request fail liên tục (tới từ cùng một địa chỉ IP).
Chạy vài tháng thì sếp thấy hệ thống chậm như rùa, yêu cầu sử dụng cache. Result lấy luôn từ cache nếu cùng một request
Rồi, gom vào một đống, lúc này hệ thống trở nên khó kiểm soát do quá bự, nhìn vào như một đống …
3. Đi tìm giải pháp
Giống như các Design Pattern khác, Chain of Responsibility hoạt động dựa trên nguyên tắc chia nhỏ trách nhiệm.
The Chain of Responsibility relies on transforming particular behaviors into stand-alone objects called handlers
Chain of Responsibility dựa trên việc chuyển đổi các hành vi cụ thể thành các đối tượng độc lập được gọi là trình xử lý (handlers). Trách nhiệm cũng đi kèm theo nha.
Trường hợp một request đi tới client, nó sẽ được xử lý tuần tự theo chuỗi thông qua các handler nhỏ.
Vấn đề này gặp rất nhiều trong cuộc sống hằng ngày. Process giao đồ ăn, process làm món ăn nhà hàng hoặc tổng đài viên.
Đầu tiên là robotic, ấn 1 để gặp phòng a, ấn 2 để gặp phòng b, vân vân. Cái này cũng được tính là một handler. Sau khi qua handler nay, next là handler tư vấn viên. Tư vấn viên sẽ hỏi cụ thể vấn đề.
Nếu vấn đề nằm ở kĩ thuật, sẽ chuyển tới handler mới là bộ phận kĩ thuật. Dựa trên ý tưởng này, ta có thiết kế
Cùng phân tích từng thành phần chính trong này ha.
Handler – khai báo interface, cái này là interface common cho tất cả các handler, mỗi handler sẽ cần phải biết handler tiếp theo để chuyển tới. Method handle(request) là method chính xử lý của handler.
BaseHandler – class này thì optional, nếu tất cả các class handler có điểm chung có thể đem vào BaseHandler.
ConcreteHandlers là phần xử lý detail cho handler. Khi đã nhận được request, mỗi handler sẽ đem vào và tiến hành xử lí. Chỉ nhận data thông qua constructor và có tính bất biến (immutable)
Client thì dễ, tùy theo application logic, tuy nhiên client có thể gửi request tới bất cứ handler nào trong chuỗi (chain). Không nhất thiết phải gửi tới handler đầu tiên.
Gửi được vậy là do sự độc lập và bất biến của từng handler
4. Tổng kết
Chain of Responsibility tỏ ra nổi bật khi hệ thống ta xây dựng muốn xử lý nhiều loại request nhau, cách mà request được gửi tới Server cũng có thể thông qua vài con đường, chứ không chỉ một.
Việc chi nhỏ thành các handler với trách nhiệm độc lập đảm bảo 2 nguyên tắc lớn là:
Single Responsibility Principle (Chữ S trong SOLID), mỗi một handler sẽ có trách nhiệm riêng độc lập nhau
Open/Closed Principle. Có thể mở rộng thêm các handler khác nhưng không động tới source đã implement.
Điểm yếu của
Pattern này cũng có một số điểm chung với Command, Mediator, Observer, là hành vi thay đổi hoặc tác động sự giao tiếp giữa sender và receivers của request.
Command establishes unidirectional connections between senders and receivers. Comman pattern thiết lập kết nối một chiều giữa người gửi và người nhận.
Mediator eliminates direct connections between senders and receivers, forcing them to communicate indirectly via a mediator object. Mediator thì loại bỏ kết nối trực tiếp giữa người gửi và người nhận, bắt phải thông qua đối tượng trung gian là mediator.
Observer lets receivers dynamically subscribe toand unsubscribe from receiving requests. Observer thì cho phép dăng kí, hủy đăng kí từ request nhận.
Bài viết được sự cho phép của tác giả Edward Thien Hoang
EBI Architecture là một kiến trúc ứng dụng hiện đại phù hợp với nhiều kiểu ứng dụng. Mục tiêu của nó là để tách business logic và presentation logic. Nó đặc biệt thích hợp cho ứng dụng web APIs, nhưng ý tưởng của EBI là có thể apply cho bất cứ dạng nào, nó không gắn liền với một nền tảng cụ thể, ứng dụng, ngôn ngữ hoặc khuôn khổ nào. Đây là một cách để thiết kế các chương trình chứ không phải là một thư viện.
Nó được biết đến rộng rãi bởi Robert C. Martin thông qua bài talk của ông về Clean Architecture. Tuy nhiên, pattern này đã được published bởi Ivar Jacobson từ năm 1992 trong một cuốn sách của ông ta với nhan đề Object-Oriented Software Engineering: A use case driven approach. Vào thời điểm đó, Jacobson gọi nó là Entity-Interface-Control, nhưng đã đổi tên thành Entity-Boundary-Interactor để tránh nhầm lẫn khái niệm “Interface” với interface trong các ngôn ngữ lập trình hoặc với “User Interface” và “Control” dễ bị nhầm lẫn với Controller trong MVC.
SỰ KHÁC NHAU SO VỚI MVC
Sự khác biệt giữa EBI và MVC là business logic của EBI được thiết kế để trở thành không phụ thuộc vào platform nào.
Để diễn giải, điều này có nghĩa là các thành phần business logic, interactors và entities, không biết trong đó họ đang truy cập phương tiện nào. Nó có thể là từ một máy chủ web, một unit test, hoặc một ứng dụng GUI.
Trái ngược với MVC, nơi luôn có sự phụ thuộc vào cơ chế đầu ra (delivery mechanism). Cho dù bạn cố gắng thế nào đi chăng nữa, bạn cũng không thể rách các Rails controller khỏi thế giới web.
Ivar Jacobson 1992, pp. 169
ENTITY
Các Entity object chứa đựng data sử dụng bởi system và tất cả các behavior hợp với data này. Mỗi Entity object đại diện cho một concept liên quan đến các vấn đề nghiệp vụ (domain) và cả việc lưu trữ persistent data. Jacobson nói rằng một Entity object chỉ nên chứa đựng logic để thay đổi data trong chính bản thân nó. Ví dụ nếu cấu trúc data của nó thay đổi thì các operation bên trong nó cũng cần phải thay đổi theo và vì vậy chúng cũng cần được đặt vào trong Entity đó.
Một điểm thú vị trong chú ý của Jacobson đã nói rằng:
Những người mới bắt đầu thường chỉ xem Entity object như nơi để contain only data structure và implement tất cả operation ở phía Controller (Interactor) object […]. Tuy nhiên, chúng ta nên tránh chuyện đó […]. Thay vào đó, phần nhiều các logic phải nên được đặt trong chính bản thân các Entity object, nơi chúng thuộc về.
Beginners may sometime only use entity object as data carriers and place all dynamic behaviour in control objects […]. This should, however be avoided. […] Instead, quite a lot of behaviour should be placed in the entity objects.
Ivar Jacobson 1992, pp. 134
BOUNDARY (INTERFACE)
Các đối tượng Boundary tạo nên giao diện (interface) với hệ thống.
[…] mọi thứ liên quan đến interface của system được đặt trong các interface object
[…] everything concerning the interface of the system is placed in an interface object
Ivar Jacobson 1992, pp. 134
Tất cả các chức năng phụ thuộc vào môi trường hệ thống (công cụ và đầu ra – tools and delivery mechanisms) thuộc về các đối tượng Boundary.
Bất kỳ tương tác nào của hệ thống với một actor sẽ đi qua một đối tượng Boundary. Như Jacobson mô tả, một actor có thể là một người sử dụng con người giống như một khách hàng hoặc một quản trị viên (nhà quản lý), nhưng nó cũng có thể là “người dùng” không phải là con người giống như một báo động, một máy in hoặc một API của bên thứ ba.
Ivar Jacobson 1992, pp. 171
Phản ánh khái niệm Boundary, xem hình 7.14 và tưởng tượng nó với 6 ranh giới thay vì 4, tôi không thể tránh kết nối nó với kiến trúc Ports & Adapters (mà tôi sẽ nói về trong một bài viết sau) xuất bản năm 2005, [13] Nhiều năm sau đó!
INTERACTOR (CONTROL)
Đối tượng Interactor sẽ giữ hành vi không ràng buộc một cách tự nhiên với bất kỳ kiểu object nào. Điều này có nghĩa là tất cả các hành vi không phù hợp với Bodunary hoặc Entity sẽ được đặt trong một hoặc nhiều đối tượng Interactor.
Ví dụ như là việc thực hiện tính toán dựa trên nhiều Entity và trả lại kết quả cho một đối tượng Boundary.
Behaviour that remains after the Interface objects and Entity objects have obtained their parts will be placed in the control objects
Ivar Jacobson 1992, pp. 185
Do đó, Jacobson nghĩ rằng Interactor object không chỉ là các đối tượng điều phối (orchestrate) các use-case mà còn là bất kỳ đối tượng nào có hành vi không phải là Boundary hay một Entity.
So với kinh nghiệm của tôi, tôi có thể nói rằng ông ta gọi các Interactor với cái mà tôi gọi là Application Services (điều phối các use-case) và Domain Services (chứa các behavior về nghiệp vụ nhưng không phải là Entity).
Lý do tại sao đối tượng Interactor trung gian này rất quan trọng là nếu chúng ta không sử dụng chúng, chúng ta sẽ phải đặt logic trong các Entity. Tuy nhiên, các Entity được sử dụng trong nhiều use-case khác nhau bởi nhiều Boundary khác nhau. Điều này gây cản trở trong việc tái sử dụng các Entity. Ví dụ như ta có logic trong Entity để phục vụ cho 1 Boundary, rất có thể sau đó lại phải thay đổi logic trong Entity đó để đáp ứng yêu cầu từ 1 Boundary khác, gây ra sự phức tạp và vi phạm nguyên tắc Single Responsibility.
TẠI SAO LẠI LÀ 3 LOẠI?
Vào thời điểm đó, Jacobson nói, các phương pháp luận khác sẽ đưa tất cả những trách nhiệm đó vào trong bản thân các Entity, tuy nhiên, ông (và các cộng sự của ông ta) đã chọn phân chia trách nhiệm đó thành 3 loại đối tượng bởi vì nó sẽ làm cho hệ thống tăng tính thích nghi với những thay đổi.
[…] all systems will change. Therefore stability will occur in the sense that all changes will be local, that is, affect (preferably) only one object in the system.
Ivar Jacobson 1992, pg. 135
Cũng giống như vậy trong mô hình MVC, Model đại diện cho toàn bộ back-end bao gồm tất cả các Entity, Servive và các mối quan hệ của chúng, mô hình EBI cho thấy Boundary là một kết nối hoàn hảo với thế giới bên ngoài và không chỉ là một khung nhìn, một controller hoặc một interface (trong ngôn ngữ lập trình). Boundary đại diện cho toàn bộ presentation layer, trong MVC tương ứng với View và Controller. Entity trong EBI là thực thể nắm giữ dữ liệu và hành vi có liên quan đến domain, trong khi đối tượng Interactor tạo kết nối giữa presentation layer và các entities, cái mà tôi gọi là Application Services và Domain Services.
EBI được thiết kế cho phía back-end, còn MVC lại được thiết kế cho phía front-end. Chúng không thể thay thế cho nhau, mà có thể kết hợp lại với nhau thành cái mà tôi gọi là View-Controller-Interactor-Entity.
Đây là bài viết trong loạt bài viết về “Tổng quan về sự phát triển của kiến trúc phần mềm“. Đây là loạt bài viết chủ yếu giới thiệu về một số mô hình kiến trúc phần mềm hay nói đúng hơn là sự phát triển của chúng qua từng giai đoạn, qua đó giúp chúng ta có cái nhìn tổng quát, up-to-date và là roadmap để bắt đầu hành trình chinh phục (đào sâu) thế giới của những bản thiết kế với vai trò là những kỹ sư và kiến trúc sư phần mềm đam mê với nghề.
Bài viết được sự cho phép của tác giả Nguyễn Hữu Khanh
Như mình đã nói trong bài viết về Tổng quan về quy trình xử lý request trong Spring Security, class UsernamePasswordAuthenticationFilter là filter sẽ đảm nhận việc authentication trong Spring Security và mặc định thông tin username và password của user sẽ được sử dụng cho quá trình authentication này. Trong các dự án thực tế thì các bạn có thể gặp những yêu cầu đăng nhập không cần sử dụng password mà sử dụng một thông tin khác như token hay một thông tin gì đó chẳng hạn… Trong các trường hợp này, chúng ta sẽ cần sử dụng một custom authentication filter để thay thế logic mặc định của Spring Security. Cụ thể như thế nào? Trong bài viết này, mình sẽ hướng dẫn các bạn cách hiện thực một custom authentication filter trong Spring Security cho trường hợp login không cần password các bạn nhé!
Đầu tiên, mình sẽ tạo mới một Spring Boot project với Spring Security Starter, Spring Web Starter và Thymeleaf Starter dependencies:
Kết quả như sau:
Để làm ví dụ cho bài viết này, mình sẽ implement chức năng cho phép user có thể nhập username và một token nào đó, được hard code, nếu đúng username và token gắn liền với username đó thì đăng nhập thành công.
Form đăng nhập
Mình sẽ sử dụng Thymeleaf với Bootstrap để hiện thực form đăng nhập này.
Trước tiên, mình sẽ sử dụng WebJars để khai báo Bootstrap dependency:
Như các bạn thấy, form login của mình lúc này, gồm 2 field là username và token. Mặc định thì phần action của form login của Spring Security sẽ gọi tới request “/login”. Mình đã chỉnh sửa một xí để nó gọi tới một request khác là “/login-token”.
Mình sẽ tạo mới một class ApplicationController để expose trang login và trang home sau khi user đăng nhập thành công như sau:
package com.huongdanjava.springsecurity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class ApplicationController {
@GetMapping("/login")
public String viewLoginPage() {
return "login";
}
@GetMapping("/")
public String hello() {
return "home";
}
}
Mình đã cấu hình để Spring Security sử dụng một custom login page thay vì trang login mặc định của nó. Các bạn có thể đọc thêm bài viết này để hiểu thêm nhé.
Cũng cần phải nói thêm là, ở đây, mình không cấu hình cho phần authentication, do đó, chỉ có một user mặc định được cung cấp bởi Spring Security là “user”. Password được hiển thị trong console log, nhưng ở đây chúng ta không sử dụng password để đăng nhập nên các bạn cũng ko cần để ý.
Custom authentication filter
Trong Spring Security thì class UsernamePasswordAuthenticationFilter extends từ class AbstractAuthenticationProcessingFilter:
nên custom authentication filter của mình cũng sẽ extend từ class AbstractAuthenticationProcessingFilter và có nội dung ban đầu như sau:
package com.huongdanjava.springsecurity;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher(
"/login-token", "POST");
public TokenAuthenticationFilter() {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
}
public TokenAuthenticationFilter(AuthenticationManager authenticationManager) {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
// TODO Auto-generated method stub
return null;
}
}
Tương tự như class UsernamePasswordAuthenticationFilter, mình định nghĩa một DEFAULT_ANT_PATH_REQUEST_MATCHER với request URL là “/login-token”, HTTP method là POST để class TokenAuthenticationFilter của mình sẽ handle request login cho tất cả các request matching với thông tin này.
Nhiệm vụ của chúng ta là add code để implement method attemptAuthentication(HttpServletRequest request, HttpServletResponse response).
Đầu tiên, mình sẽ lấy thông tin username và token từ request của user.
Sau đó thì mình sẽ validate những thông tin này sử dụng phương thức validateUsernameAndToken(username, token):
private boolean validateUsernameAndToken(String username, String token) {
try {
userDetailsService.loadUserByUsername(username);
} catch (UsernameNotFoundException e) {
this.logger.debug("Failed to find user '" + username + "'");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
Map<String, String> map = new HashMap<>();
map.put("123", "user");
if (map.containsKey(token)) {
String user = map.get(token);
if (user.equals(username)) {
return true;
}
}
this.logger.debug("No user '" + username + "' associate with the token " + token);
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
Ở đây, đầu tiên, mình sẽ validate xem username có tồn tại trong hệ thống hay không sử dụng interface UserDetailsService. Đây là class quản lý thông tin user của ứng dụng. Như mình đã nói ở trên, mình sử dụng cấu hình mặc định của Spring Security cho phần authentication nên lúc này chỉ có 1 user duy nhất trong ví dụ là “user”. Nếu user mà người dùng sử dụng không tồn tại trong hệ thống, chúng ta sẽ throw exception.
Để đơn giản cho ví dụ, mình hard code token associate với user “user” là 123 để kiểm tra thông tin đăng nhập của user. Chỉ có username “user” và token “123” thì người dùng mới đăng nhập thành công.
Nếu thông tin token người dùng nhập vào không đúng, một exception khác cũng được throw ra.
Nếu thông tin người dùng nhập vào đúng hết, chúng ta sẽ cho phép authentication thành công:
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
var authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
Ở đây, mình sử dụng lại class UsernamePasswordAuthenticationToken để build đối tượng Authentication, xác nhận thông tin đăng nhập thành công. Chúng ta cũng lưu lại thông tin về sessionId và IP đang đăng nhập của user sử dụng class WebAuthenticationDetailsSource.
Toàn bộ nội dung của class TokenAuthenticationFilter như sau:
package com.huongdanjava.springsecurity;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher(
"/login-token", "POST");
@Autowired
private UserDetailsService userDetailsService;
public TokenAuthenticationFilter() {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
}
public TokenAuthenticationFilter(AuthenticationManager authenticationManager) {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
this.logger.info("Authenticating ...");
String username = getUsername(request);
String token = request.getParameter("token");
validateUsernameAndToken(username, token);
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
var authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
return authentication;
}
private String getUsername(HttpServletRequest httpRequest) {
String username = httpRequest.getParameter("username");
if (username == null) {
return "";
}
return username;
}
private boolean validateUsernameAndToken(String username, String token) {
try {
userDetailsService.loadUserByUsername(username);
} catch (UsernameNotFoundException e) {
this.logger.debug("Failed to find user '" + username + "'");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
Map<String, String> map = new HashMap<>();
map.put("123", "user");
if (map.containsKey(token)) {
String user = map.get(token);
if (user.equals(username)) {
return true;
}
}
this.logger.debug("No user '" + username + "' associate with the token " + token);
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
Bây giờ, các bạn có thể add filter mới của chúng ta vào Spring Security để thay thế cho filter login mặc định của nó như sau:
Như các bạn thấy, mình sử dụng method addFilterAt() của class HttpSecurity để add custom filter này.
Trong phần khai báo bean của class TokenAuthenticationFilter, mình cũng set authentication manager mặc định của Spring Security. Mặc định thì successHandler sử dụng class SavedRequestAwareAuthenticationSuccessHandler(), còn failureHandler là SimpleUrlAuthenticationFailureHandler nhưng mình cần set lại failureUrl nên mình phải tạo mới lại đối tượng này để gán failureUrl.
OK, đến đây thì chúng ta đã hoàn thành ví dụ của bài viết này. Các bạn có thể chạy ứng dụng để kiểm tra kết quả.
Của mình, nếu nhập username là “user” và token là “123”. Kết quả sẽ như sau:
Nếu không đúng thông tin đăng nhập, kết quả sẽ như sau:
Phỏng vấn xin việc luôn có rất nhiều vấn đề để chia sẻ và thảo luận, đây cũng là một trong số nhiều vấn đề khiến ứng viên cảm thấy lo lắng nhất trong quá trình tìm việc của mình. Để chuẩn bị tốt cho một buổi phỏng vấn, ngoài những điều nên làm, những yếu tố cần có, nguyên tắc 4 không khi xin việc dưới đây cũng sẽ giúp bạn trau dồi bản thân tốt hơn và gia tăng khả năng trúng tuyển.
Nắm rõ những điều tối kị khi phỏng vấn để đạt kết quả tốt hơn
1. Không tìm hiểu trước về thông tin của công ty
Không ít ứng viên hiện nay, nhất là với các bạn trẻ vừa ra trường chưa có nhiều kinh nghiệm phỏng vấn xin việc, không có sự chuẩn bị chu đáo với những buổi phỏng vấn. Họ chỉ nghĩ một cách đơn thuần rằng sẽ làm thật tốt những câu hỏi chuyên môn và trả lời các vấn đề về kỹ năng cá nhân mình có là có thể gây ấn tượng với nhà tuyển dụng. Suy nghĩ này đúng nhưng chưa đủ.
Bên cạnh những gì bạn có, nhà tuyển dụng còn quan tâm đến những gì giá trị mà bạn mang lại có tương xứng với những gì mà công ty đang mong muốn hay không. Điều đó cũng đồng nghĩa với việc những kỹ năng, những kiến thức chuyên môn bạn có sẽ góp phần cho sự phát triển của công ty như thế nào. Ứng viên có thể chia sẻ rất nhiều thứ về những gì họ có thể làm được nhưng nếu công ty không cần những điều đó thì sẽ rất khó để người phỏng vấn đánh giá cao bạn.
Vậy nên, một trong những nguyên tắc 4 không khi xin việc mà mọi ứng viên cần đặc biệt lưu ý đó là không tìm hiểu trước những thông tin về công ty – đây là một thiếu sót to lớn khiến kết quả phỏng vấn tệ hơn. Hãy tận dụng mọi nguồn thông tin bạn có, từ online đến offline, từ website, fanpage của công ty đến những người quen hay những ai biết thông tin về công ty để tham khảo. Từ những giá trị về sứ mệnh, mục tiêu phát triển và các sản phẩm công ty đang sáng tạo, bạn có thể trả lời phỏng vấn ấn tượng hơn trong mắt nhà tuyển dụng.
2. Không thành thật khi trả lời phỏng vấn
Bạn có thể nói quá một chút về những kỹ năng của bản thân để gây ấn tượng trong mắt nhà tuyển dụng nhưng đừng nói dối, đừng “nói không thành có”. Không thành thật là lý do khiến bạn trượt phỏng vấn nhanh nhất. Người phỏng vấn đều là những người đã có nhiều kinh nghiệm trong ngành nghề, họ đủ năng lực và hiểu biết để nhìn ra bạn đang thiếu thành thật trong câu trả lời của mình. Do đó, trong mọi trường hợp, thành thật vẫn luôn là yếu tố được đánh giá cao.
Chẳng hạn, rất nhiều ứng viên phỏng vấn hiện nay khi được hỏi đâu là điểm yếu của bản thân đều trả lời rằng mình quá cầu toàn và nghiêm khắc. Đây là một mẫu câu trả lời hoàn toàn rập khuôn mà chúng ta có thể dễ dàng tìm kiếm được với bất kì hướng dẫn trả lời phỏng vấn nào trên mạng. Trả lời theo hướng này bạn sẽ không được nhà tuyển dụng đánh giá cao vì không thể hiện được bất cứ dấu ấn cá nhân nào của bạn.
Một số ứng viên khi được hỏi về kỹ năng và chuyên môn làm việc thường cố gắng tâng bốc năng lực của bản thân để thuyết phục nhà tuyển dụng. Tuy nhiên, có đôi lúc việc trả lời thiếu thành thật hoặc đưa ra những khả năng mình không có chính là cái bẫy cho bản thân ứng viên. Người phỏng vấn hoàn toàn có thể dựa trên những kinh nghiệm bạn đưa ra để tiếp tục đặt câu hỏi và bạn không thể trả lời. Thiếu thành thật khiến các câu trả lời của ứng viên không có sự nhất quán và nhà tuyển dụng sẽ phát hiện ra vấn đề ngay lập tức.
3. Không chủ động và quá tự ti về năng lực của bản thân
Thực chất một cuộc phỏng vấn đúng nghĩa sẽ là sự trao đổi thông tin giữa hai bên trên tinh thần tìm hiểu về nhau và những lợi ích mà hai bên có thể mang lại cho nhau. Do đó, người phỏng vấn cũng sẽ không đánh giá cao những người thiếu đi sự chủ động và chỉ tập trung vào trả lời mọi câu hỏi mà họ đưa ra. Trong nhiều tình huống, ứng viên cần phải linh động và đặt ra những câu hỏi phù hợp để khai thác thêm thông tin từ nhà tuyển dụng.
Nếu suôn sẻ, đây sẽ là công việc mà bạn gắn bó trong tương lai, chính vì thế việc tìm hiểu những thông tin mà mình chưa chắc chắn hay còn đang thắc mắc hoàn toàn là điều cần thiết. Đặt ra những câu hỏi hợp lí và đúng lúc cũng là cách để bạn thể hiện sự chuyên nghiệp của bản thân cũng như sự hứng thú với công việc.
Bên cạnh đó, hãy tự tin vào năng lực của bản thân và chia sẻ những gì bạn đã làm được và có thể làm được trong tương lai. Đây không phải là lúc để bạn khiêm tốn hay tự ti về năng lực của mình.
Đây là nguyên tắc cực kỳ quan trọng mà bất cứ ứng viên nào cũng không nên phạm phải. Bạn có thể cảm thấy khó chịu hay bực tức với nhiều vấn đề ở công ty cũ, nhưng cuộc phỏng vấn chắc chắn cũng không phải là nơi để bạn trút bỏ những bực dọc này. Sẽ không có người phỏng vấn nào đánh giá cao một ứng viên chỉ biết nói xấu công ty cũ và sếp cũ.
Hãy kiềm chế bản thân và nói vừa đủ về công ty cũ, đó sẽ là cách phương án hợp lý nhất trong buổi phỏng vấn khi nói về công ty cũ cũng như sếp cũ. Đó cũng là cách để bạn tạo sự thiện cảm trong mắt nhà tuyển dụng và không khiến họ cảm thấy khó chịu hay thiếu niềm tin ở bạn.
Thực hiện tốt nguyên tắc 4 không khi xin việc cũng đồng nghĩa với việc bạn đã tránh được những điều tối kị khi phỏng vấn. Do đó, nếu có được sự chuẩn bị kỹ càng các kỹ năng này kết hợp thêm với kiến thức chuyên môn, cơ hội thành công của bạn sẽ càng cao hơn nữa. Đón đọc thêm nhiều bài viết hữu ích khác với những kinh nghiệm phỏng vấn hay ho cùng TopDev nhé.
Trong bài viết tôi có sử dụng một số thuật ngữ chuyên ngành, có thể những bạn sinh viên không biết về những thuật ngữ này, những ai mà không biết thì hãy để lại bình luận để tôi giải thích thêm nhé.
Sau khi được nhận vào chính thức thì tôi ngồi chơi khoảng một tuần thì được join vào dự án mới, đây là dự án đầu tiên của tôi. Có phải các bạn đang thắc mắc tại sao các phần trước tôi nói rằng tôi học chủ yếu là java, đi thử việc cũng là java nhưng tiêu đề lại ghi là ngôn ngữ delphi. Đúng như các bạn nghĩ, tôi đã tham gia vào dự án được viết bằng ngôn ngữ delphi. Cuốc sống không phải lúc nào cũng màu hồng, đôi khi bạn tính không bằng công ty tính, cứ tưởng vào sẽ được làm dự án về java, ai ngờ.
Delphi là một ngôn ngữ phát triển dựa trên pascal và mở rộng hướng đối tượng và được sử dụng để tạo ra các phần mềm cho windown, linux, MAC, android, iOS và đây cũng là lần đâu tiên tôi biết về nó.
Đây là một dự án đã lâu năm về trước, đã được phát triển bởi một công ty nào đó mà tôi cũng không biết, dự án phát triển để chạy trên window, nó tương tự như phần mềm viết bằng c#, còn ở đây nó viết bằng delphi. Công việc của team chúng tôi đó là convert project từ delphi 6 lên delphi XE4. Các bạn có thể hiểu đơn giản đó là việc nâng cấp từ version nhỏ lên version cao. Nếu các bạn biết về c# thì cũng tương tự convert từ winform lên WPF.
Tôi được vào team của một chị senior quản lý, dự án gồm chị và hai ông anh, một ông làm một năm rưỡi và một ông làm tầm một năm và tôi thằng có số năm kinh nghiệm là zero. Tôi bắt đầu làm quen với mọi người, cái anh ngồi gần tôi khá là khó bắt chuyện, tôi hỏi han đủ thứ các kiểu như tên, tuổi, làm ở đây lâu chưa. Lý do vì sao tôi phải làm như vậy, đó là tôi mới vào làm nên nhiều thứ còn chưa biết, phải làm quen để sau này có gì khó còn hỏi chứ. Cái này cũng rất quan trọng là mọi người. Cái anh này sau này lại thân với tôi, cực kỳ vui tính, luôn chỉ cho tôi những gì mà tôi hỏi.
Quay trở lại với dự án để không bị lạc trôi, như thường lệ của những dự án khác, buổi đầu tiên luôn gọi là kick off dự án. Ở đây kick off mang ý nghĩa là bắt đầu một dự án mới, các anh chị quản lý sẽ giới thiệu về dự án, các thành viên, độ dài của dự án khoảng bao lâu, khách hàng là ai. Còn vấn đề số tiền của dự án thu được thì là bí mật của công ty, chỉ có leader trở lên mới biết về vấn đề này. Trở lại với vấn đề chính của dự án, trước đây không lâu đã có một dự án tương tự như cái mà tôi đang làm, nhớ không nhầm cũng là của khách hàng này luôn. Cách convert đã có, mọi tài liệu của dự án cũ đều được lưu giữ lại cẩn thận.
Sau khi họp xong thì chúng tôi bắt đầu làm dự án, schedule thì được chị quản lý lên cho mọi người. Schedule là lịch trình được sắp xếp sẵn, ví dụ như ngày hôm nay sẽ làm công việc A, mai sẽ làm công việc B. Việc lên schedule là của quản lý, còn việc tuân thủ đúng thời gian của schedule là công việc của bạn. Tôi được giao màn hình mà các anh chị trong đó nói là màn hình khó nhất, số tôi rất nhọ, khi đi làm lúc nào cũng được giao cái khó nhất. Ok thôi, đã đi làm thì giao cái nào thì quất cái đó, những dự án sau tôi sẽ kể các bạn nghe tôi làm fail dự án như thế nào vì giao cho tôi những cái quá khả năng. haha.
Mọi tài liệu, source code của dự án đều được lưu trữ trên SVN, là server nội bộ của công ty, tôi phải học cách sử dụng nó. Những bạn nào chưa biết về cái này thì nên học cách sử dụng. Một là SVN, thứ hai là GIT. Học cả hai cái này thì càng tốt, tùy công ty họ sẽ sử dụng cái nào. Tôi tiến hành checkout source code về máy, cài đặt môi trường, tool để chạy source code.
Vấn đề đầu tiên khi tôi gặp phải khi chạy ứng dụng lên để xem thử màn hình tôi làm nó trông như thế nào là không lên được màn hình =)). Tôi bị bí chỗ này luôn, đối với một gà mờ như tôi thì việc nghiên cứu framework thì được, chứ chuyển sang một ngôn ngữ mới thì khá là chua. Một lúc sau chạy mãi không được, tôi bắt đầu lân la đi hỏi cái anh ngồi cạnh, tôi gọi là anh B và tiến hành debug cho tôi, lúc này tôi vẫn chưa biết debug là gì nhé. Tôi thấy anh ấy bấm bấm cái gì đó hiện ra hình tròn tròn, rồi khi chạy chương trình lên thì nó tô màu một dòng code, sau đó nhấn phím để nó chạy qua từng dòng code. Sau đó tôi mới biết đây là debug, tôi thấy được sự lợi hại của nó mang lại, cái này ở trường tôi thì họ không dạy, tôi cũng không biết về nó để mà tìm hiểu. Giải thích một chút về debug cho những bạn chưa biết. Debug có nghĩa là gỡ lỗi, là công cụ của hầu hết ngôn ngữ lập trình nào cũng có, nó sẽ chạy qua từng dòng code của bạn, xem được dữ liệu của cái biến, list, array…Từ đó giúp chúng ta có thể phán đoán được lỗi mà mình đang gặp phải. Dĩ nhiên cái này cũng phải dựa trên kinh nghiêm làm việc mà phán đoán được.
Quay trở lại với màn hình của tôi, sau khi một vài phút anh B hỗ trợ thì cuối cùng cũng tìm được là thông tin kết nối database tôi không chính xác, cái ngu của tôi lòi ra ở đây, tôi nhập thông tin kết nối database sai. Làm mất thời gian của anh B, bị ãnh sạc cho một trận. Nhưng chỉ nói vui thôi và nói lần sau rút kinh nghiệm, sau khi kết nối được database và chạy lên được màn hình thì tôi bắt đầu convert, giữa hai phiên bản Delphi 6 và Delphi XE4 thì nó khác nhau về các control như button, label…Đồng nghĩa với các library của các control đó cũng khác nhau. Việc chỉnh sửa không đơn giản là bạn thay thế các control cũ mà bạn phải comment những dòng chỉnh sửa lại theo một format nhất định. Trường hợp là những dòng code có sẵn mà muốn xóa thì không phải xóa hẳn khỏi file mà là sẽ comment nó lại. Hãy xem ví dụ bên dưới.
// 20190205 Delete Start
// source code cũ của bạn
// 20190205 Delete End
Trường hợp đối với những dòng code mà mình muốn thêm vào sẽ được comment như sau.
// 20190205 Add Start
source code mới của bạn
// 20190205 Add End
Trường hợp đối với những dòng code mà mình muốn chỉnh sửa sẽ được comment như sau.
// 20190205 Change Start
// source code cũ của bạn
source code mới của bạn
// 20190205 Change End
Như đã nói thì tôi được giao cho một màn hình khó, sau khi convert tất cả source code thì cần phải chạy lại màn hình để kiểm tra các chức năng của màn hình đó xem có giống y chang trước khi convert hay không. Tôi tiến hành chạy màn hình của mình, kiểm tra thì thấy chức năng chạy khác so với trước khi convert, tôi không biết nguyên nhân vì sao lại như vậy, mày mò khá lâu và cũng không thể tìm ra nguyên nhân. Tôi liền nghĩ tới cầu cứu anh B, tuy nhiên tôi chợt nghĩ, mọi người ai cũng có công việc của họ, làm như vậy là làm phiền người khác, tốn thời gian quý báu của mọi người. Tôi liền tiến hành học cách debug, tại thời điểm này thì chỉ có debug thì mới có thể tìm ra được nguyên nhân mà thôi, việc tiếp cận một ngôn ngữ mới cũng không dễ dàng gì chứ đừng nói đến việc debug của ngôn ngữ đó đối với một người mới bước chân vào con đường lập trình chuyên nghiệp. Tôi tự mày mò cách đặt debug, chăm chỉ debug để chạy qua từng dòng code, tuy nhiên tôi khá lạ lẫm với các dòng code này vì kỹ năng đọc để hiểu được đoạn code đó chưa có. Loay hoay cả buổi mà vẫn chưa tìm được nguyên nhân, lần đầu tiên tôi cảm thấy mình kém cỏi như vậy, tôi lo lắng về deadline, gần tới deadline mà vẫn chưa làm xong màn hình của mình và cũng lần đầu tiên tôi cảm thấy lo sợ như vậy, lo sợ không hoàn thành được việc, lo sợ sẽ bị công ty đuổi việc. Tới nước này tôi bắt buộc phải cầu cứu tới anh B, tôi tranh thủ lúc anh B đang rảnh liền qua hỏi, anh B cũng hướng dẫn tận tình cách debug, chỉ cho tôi cách xem dữ liệu của từng biến, từng hàm trả về là gì, cuối cùng đã tìm ra nguyên nhân dẫn đến sai khác. Màn hình của tôi cũng đã hoàn thành đúng deadline, tôi cũng đã có một chút kinh nghiệm về cách debug.
Sau khi code xong một màn hình thì chị quản lý sẽ review lại source code của tôi, xem cách tôi convert đã đúng chưa, comment đúng chuẩn chưa, có dư thừa code gì hay không, dư một dòng enter thôi cũng bắt phải sửa lại cho giống source code trước khi convert. Ở công ty tôi được biết tới một phần mềm tên là Beyond compare, phần mềm này cho phép bạn so sánh giữa các file source code trước và sau khi chỉnh sửa, vì vậy mình sẽ biết được là đã chỉnh sửa những dòng nào và ở vị trí nào. Quán trình review source code rất là chặt chẽ, sai một tí xíu thôi cũng phải sửa lại mặc dù nó không ảnh hưởng tới logic của màn hình, ban đầu tôi hơi khó chịu với cách làm như vậy, nhưng thôi kệ, đi làm công ăn lương thì phải tuân thủ, dù là những cái khắt khe hơn cũng phải tuân thủ.
Tôi cũng đã nhận thêm màn hình mới cho mình sau khi đã hoàn thành màn hình đầu tiên, giờ đây tôi cũng đã dần quen với ngôn ngữ delphi và cách debug, những màn hình sau không có khó khăn gì cả, dự án này thật ra nghiệp vụ không quá phức tạp, cái khó của tôi là cách đọc source code để hiểu nó đang dùng để làm gì và dĩ nhiên vì đó là ngôn ngữ chưa học qua nên giai đoạn đầu là giai đoạn khó khăn cho việc này.
Sau khi tất cả các màn hình đã được convert xong thì tiếp đến là giai đoạn tạo testcase và tiến hành test. Vì công ty tôi đang làm không giống như những công ty khác, các công ty khác họ sẽ có đội ngũ test riêng, tuy nhiên công ty chúng tôi ôm xô luôn cả việc đó. Chị quản lý đã viết sẵn testcase cho một màn hình và chúng tôi sẽ bắt chước để viết theo cho những màn hình còn lại. Testcase đại loại là sẽ test những chức năng của màn hình đã convert chạy giống với những chức năng của màn hình đó trước khi convert là được. Sau khi testcase đã viết xong thì chúng tôi bắt đầu test, công việc này tương đối tốn thời gian, có khi thời gian còn nhiều hơn cả code, chúng tôi test thì phải chụp cả hình ảnh để làm bằng chứng là đã test và lưu vào file excel, mỗi testcase chụp cả hình ảnh của chức năng trước khi convert và sau khi convert, để khẳng định rằng trước và sau khi convert thì logic không có gì thay đổi, trường hợp test có bug thì sẽ fix bug. Sau khi test xong thì chị quản lý sẽ review lại những hình chụp có chính xác với testcase hay chưa, nếu chưa đúng thì sẽ được yêu cầu chụp lại.
Và dự án của chúng tôi cũng đã đi đến 95% chặng đường, còn lại sẽ do chị quản lý sẽ lo. Tới ngày giao hàng, sẽ gom source code, tài liệu gồm các file testcase và file chụp hình để giao cho khách hàng. Sau khi giao hàng xong khách hàng sẽ nghiệm thu và sẽ gửi bug về công ty nếu có. Kết thúc dự án mọi người được nghỉ ngơi vài ngày và sau đó sẽ được cho làm những dự án khác. Đến đây thì cũng kết thúc bài viết về dự án đầu tiên, hy vọng sẽ có ích cho các bạn sinh viên sau này khi đi làm.
Sau bài viết mình xin rút ra những kinh nghiệm dành cho những bạn sinh viên đang học hoặc sắp đi làm như sau.
1. Học cách debug cho ngôn ngữ của mình đang học, để lúc đi làm khi gặp bug mà không giải quyết được thì phải dùng đến debug. Chứ đừng như tôi, ra trận mới học.
2. Học ít nhất một trong hai source control là git và svn. Đây là những công cụ quản lý source và nơi lưu trữ source code cho bạn, ở đây bạn có thể quản lý được các version code của mình.
3. Lúc mới vào công ty nên làm quen một vài người bạn để họ có thể chỉ cho bạn những cái bạn hỏi trong tâm trạng vui vẻ
Các bạn nhớ like fanpage để theo dõi những bài viết mới nhất nhé.
Bài viết được sự cho phép của blogchiasekienthuc.com
Hiện nay, có rất nhiều công cụ rút gọn URL trên Internet, việc rút gọn các URL dài loằng ngoằng thành những URL ngắn và dễ nhớ sẽ giúp bạn lưu trữ hay là chia sẻ URL được dễ dàng hơn và chuyên nghiệp hơn.
Tuy nhiên, các công cụ rút gọn URL thường bị giới hạn ở một đường link duy nhất, nên nếu bạn có một danh sách nhiều URL khác nhau thì việc chia sẻ nó cũng khá là bất tiện và mất khá nhiều thời gian.
Vì vậy mà trong bài viết này, mình sẽ chia sẻ với các bạn một số công cụ rút gọn link có hỗ trợ hợp nhất các URL khác nhau thành một URL duy nhất.
Trong khuôn khổ của bài viết này mình sẽ giới thiệu với bạn 3 công cụ, mỗi công cụ mình giới thiệu đều có những điểm mạnh và điểm yếu riêng, nên bạn cứ đọc rồi tìm ra dịch vụ nào phù hợp nhất với bạn thì dùng nhé.
#1. ListURLs
Link truy cập trang web: https://listurls.com/
ListURLs là công cụ rút gọn URL đầu tiên mà mình muốn giới thiệu, bạn có thể dễ dàng thêm tiêu đề và thêm không giới hạn các URL vào danh sách.
Bạn có thể sử dịch vụ này dụng ngay mà không cần phải tạo tài khoản, ưu điểm của nó là nhanh gọn, nhưng như vậy cũng đồng nghĩa với việc bạn không thể quản lý các URL mà bạn đã tạo ra được.
Đây là giao diện khi bạn truy cập vào ListURLs.com
Thêm tiêu đề ở phần Add title, và các URL mà bạn muốn rút gọn vào danh sách.
Bạn cũng có thể thêm tiêu đề và xóa từng URL ở các ô tính năng bên cạnh. Sau khi hoàn tất thì bạn nhấn Create link here để tạo link rút gọn.
Cuối cùng thì bạn chỉ cần copy link và chia sẻ nó, khi ai đó truy cập vào thì họ sẽ được ListURLs hiển thị một danh sách với tất cả các link mà bạn từng cho vào.
Tổng kết: Như mình đã nói bên trên, sau khi bạn tạo danh sách link trên thì bạn sẽ không có quyền xóa hay chỉnh sửa nữa. có thể nói là các link này sẽ tồn tại vĩnh viễn, bạn cũng không thể chỉnh sửa phần sau của link mà phải theo mặc định của trang web.
#2. MergeURL
Link truy cập trang web: https://mergeurl.com/
MergeURL cũng hoạt động tương tự như ListURLs, bạn có thể sử dụng mà không cần phải đăng ký nên cũng sẽ không có chức năng chỉnh sửa hay quản lý, và bạn chỉ có thể thêm tối đa 5 URL vào danh sách rút gọn.
Nếu trong 12 tuần mà URL không có lượt truy cập nào thì hệ thống sẽ tự động xóa URL rút gọn đấy.
Cách sử dụng thì vô cùng đơn giản thôi. Bạn lần lượt thêm các URL cần rút gọn vào => và nhấn Add.
Sau khi hoàn tất thì bạn nhấn Merge and Shorten để tạo URL rút gọn.
URL sau khi được tạo ra sẽ nằm phía trên danh sách bạn vừa nhập. Sẽ có hai đường dẫn và mã QR để bạn chọn.
To open: Thì khi bạn dán đường dẫn vào để duyệt web thì nó sẽ tự động bật 5 tab cùng một lúc, nhưng để làm được như vậy thì bạn cần thêm trang web mergeurl.com vào danh sách cho phép trong phần tính năng cửa sổ bật lên và chuyển hướng của trình duyệt web bạn đang sử dụng.
To view: Nó sẽ hiển thị các trang như danh sách bạn vừa nhập, và truy cập trang nào thì là quyền của bạn chứ nó không tự động bật lên.
Tổng kết: Công cụ này chỉ thêm được 5 link vào danh sách rút gọn, bạn nên gửi link ở phần To view cho người khác, vì cửa sổ tự động bật lên sẽ bị trình duyệt web chặn lại nên sẽ mất kha khá thời gian nếu người ấy không rành về thiết lập trình duyệt web.
#3. BridgeURL
Link truy cập trang web: https://bridgeurl.com/
BridgeURL cũng là trang web rút gọn URL khác mà mình muốn giới thiệu với các bạn, nếu bạn muốn quản lý các liên kết thì nhớ đăng ký và đăng nhập trước khi rút gọn link nhé.
Cách hoạt động cũng tương tự như những trang web khác. Bạn thêm phần tiêu đề ở đầu, thêm tiếp các đường link cần rút gọn, xác nhận Tôi không phải là người máy => rồi nhấn CREATE LINK.
Copy đường dẫn để lưu trữ hoặc chia sẻ.
Khi bạn truy cập vào đường link đã rút gọn đấy, nó sẽ chuyển đến trang tập hợp các liên kết mà bạn đã thêm vào lúc đầu. Nhìn chung thì giao diện đẹp hơn các trang ở phía trên 🙂
#4. Lời Kết
Như vậy là mình đã giới thiệu xong cho bạn 3 trang web giúp bạn kết hợp nhiều link thành một link rút gọn duy nhất, miễn phí 100% rồi nhé.
Mỗi công cụ đều mang tới những chức năng và những trải nghiệm sử dụng khác nhau, bạn nên tìm hiểu trước để chọn ra dịch vụ phù hợp với bạn nhé. Chúc bạn thực hiện thành công.
Là một kỹ sư kiểm thử phần mềm chuyên nghiệp, bạn nên biết sơ lược lịch sử về công nghệ phần mềm, bởi vì kiểm thử phần mềm gắn liền với tất cả các giai đoạn của công nghệ phần mềm. Công nghệ phần mềm đã phát triển qua 4 giai đoạn: thập niên 50-60, giữa thập niên 60 đến cuối những năm 70, giữa thập niên 70 đến giữa những năm 80, và giữa thập niên 80 đến hiện tại. Mỗi giai đoạn đều có những đặc trưng riêng biệt của nó, nhưng trong những năm qua phần mềm đã tăng lên về số lượng và độ phức tạp. Một số vấn đề phổ biến với gần như tất cả các giai đoạn và sẽ được thảo luận dưới đây.
Cuộc khủng hoảng phần mềm những năm 1960 khi những lý do chính cho tình hình này là việc ứng dụng kỹ thuật phần mềm vào thực tiễn ít được chấp nhận. Trong giai đoạn đầu của công nghệ phần mềm, có rất nhiều sự quan tâm đến máy tính, rất nhiều đoạn mã lệnh được viết nhưng không được công bố một cách chuẩn mực. Sau đó, vào đầu thập niên 70 rất nhiều các chương trình máy tính bắt đầu ngừng hoạt động và người ta mất niềm tin, như vậy, một cuộc khủng hoảng đã được công bố. Vì nhiều lý do dẫn đến cuộc khủng hoảng bao gồm:
– Phần cứng tiến bộ nhanh hơn khả năng xây dựng phần mềm cho phần cứng này.
– Khả năng xây dựng ứng dụng không theo kịp với nhu cầu.
– Tăng cường sự phụ thuộc vào phần mềm.
– Cuộc đấu tranh để xây dựng phần mềm đáng tin cậy và chất lượng cao.
– Thiết kế yếu kém và các nguồn tài nguyên không tương xứng.
Dự án phần mềm cơ bản được coi là thất bại nếu dự án được chấm dứt vì chi phí (vượt quá 50% dự toán) hoặc ngày phát hành quá trễ so với dự định. Một số ví dụ về những thất bại bao gồm sự thất bại của hệ thống kiểm soát không lưu, thất bại của phần mềm y tế, và thất bại trong phần mềm viễn thông. Lý do chính cho những thất bại được đề cập ở trên là do áp dụng công nghệ phần mềm không tốt. Một số cách thức ứng dụng công nghệ phần mềm tồi tệ nhất bao gồm:
– Không có phần mềm đo lường dữ liệu quá khứ.
– Từ chối dự toán chi phí chính xác.
– Không sử dụng các công cụ tính toán và lập kế hoạch tự động.
– Gây áp lực quá mức, tiến độ bất hợp lý và không hiểu rõ các yêu cầu của người sử dụng.
– Thất bại trong việc theo dõi tiến độ và thực hiện quản lý rủi ro.
– Không sử dụng đánh giá thiết kế và kiểm tra mã.
Để tránh những thất bại và cải thiện kết quả, điều cần thiết là sự hiểu biết tốt hơn về quy trình, các kỹ thuật ước lượng tốt hơn cho chi phí, thời gian và các biện pháp đo lường chất lượng. Hiện nay, một số lượng lớn các vấn đề tồn tại do một quy trình phần mềm hỗn loạn và sự thành công không thường xuyên phụ thuộc vào nỗ lực cá nhân. Vì vậy để dự án phần mềm thành công, tập trung vào quy trình là cần thiết. Sự tập trung này sẽ giúp ích trong việc dự đoán kết quả, xu hướng dự án và đặc điểm của dự án. Các quy trình đã được xác định và thông qua cần phải được quản lý tốt và sau đó nó sẽ phát huy tác dụng.
Từ đây chúng ta kết luận rằng quy trình tốt có thể cứu dự án phần mềm khỏi thất bại. Cần lưu ý rằng quy trình một mình nó không thể giúp chúng ta tránh được tất cả các vấn đề, cần thay đổi quy trình để có thể thích ứng với những nhu cầu khác nhau, hoàn cảnh khác nhau. Vì vậy để thực hiện một sản phẩm thành công, một sự kết hợp giữa quy trình và công nghệ là cần thiết.
Sau khi nói về quy trình phần mềm tổng thể, việc xác định vai trò của kiểm thử phần mềm là quan trọng trong việc sản xuất phần mềm chất lượng. Định nghĩa thử nghiệm như sau: “Kiểm thử hay Thử nghiệm – Một phương pháp xác minh bằng cách sử dụng tập hợp những điều kiện có kiểm soát với mục đích tìm kiếm lỗi. Đây là phương pháp được ưa chuộng nhất để xác minh các yêu cầu chức năng và hiệu suất. Kết quả thử nghiệm là tài liệu chứng minh rằng các yêu cầu đã được đáp ứng và có thể được lặp đi lặp lại. Các dữ liệu kết quả có thể được xem xét bởi tất cả các bên có liên quan để xác nhận”
Có thể có nhiều định nghĩa về kiểm thử phần mềm và theo thời gian, nhưng tốt nhất hãy bắt đầu bằng cách định nghĩa kiểm thử và sau đó bổ sung tùy thuộc vào các yêu cầu hoặc nhu cầu.
Trong các bài viết trước, chúng ta đã cùng tìm hiểu về Direct Exchange, Fanout Exchange và Topic Exchange. Trong bài này, tôi sẽ giới thiệu với các bạn một loại exchange rất mạnh mẽ khác của RabbitM là Headers Exchange.
Header exchange (amq.headers) được thiết kế để định tuyến với nhiều thuộc tính, để dàng thực hiện dưới dạng header của message hơn là routing key. Header exchange bỏ đi routing key mà thay vào đó định tuyến dựa trên header của message. Trường hợp này, broker cần một hoặc nhiều thông tin từ application developer, cụ thể là, nên quan tâm đến những tin nhắn với tiêu đề nào phù hợp hoặc tất cả chúng.
Headers Exchange rất giống với Topic Exchange, nhưng nó định tuyến dựa trên các giá trị header thay vì routing key.
Một Message được coi là phù hợp nếu giá trị của header bằng với giá trị được chỉ định khi ràng buộc.
Flow của một Message trong Headers Exchange như sau:
Một hoặc nhiều Queue được tạo và binding tới một Headers Exchange sử dụng các header property (H).
Một Producer sẽ tạo một Message với các header property (MH) và publish tới Exchange.
Một Message được Exchange chuyển đến Queue nếu Header H match với Header MH.
Consumer đăng ký tới Queue để nhận Message.
Có 2 loại matching được sử dụng để kiểm tra một Header của binding queue có match với một header từ message đến:
any: tương tự như logic OR, được biểu diễn trong các ràng buộc header property là {“x-match“, “any“, …} . Nghĩa là, một Message được gửi tới Exchange phải chứa ít nhất một trong các header mà Queue được liên kết, sau đó Message sẽ được chuyển đến Queue.
all: tương tự như logic AND, được biểu diễn trong các ràng buộc header property là {“x-match“, “and“, …} . Nghĩa là, các Message có tất cả các header được liệt kê của nó sẽ được chuyển tiếp đến Queue.
Ví dụ Topic Exchange trong RabbitMQ
Trong ví dụ này, tôi tạo một Headers Exchange có tên GPCoderHeadersExchange, tạo 3 Queue binding tới Headers Exchange này:
QDeveloper : Queue này sẽ nhận tất cả message có header là {“dev”, “Developer Channel”} hoặc {“general”, “General Channel”}.
QManager : Queue này nhận tất cả message có header là {“dev”, “Developer Channel”} hoặc {“general”, “General Channel”} hoặc {“manager”, “Manager Channel”}.
QPublished : Queue này nhận tất cả message có header là {“dev”, “Developer Channel”} và {“access”, “publish”}.
Một số class của chương trình:
ConnectionManager : hỗ trợ tạo Connection đến RabbitMQ.
HeadersExchangeChannel : class util hỗ trợ tạo Echange, Queue, binding Queue đến Exchange, publish/ subscribe message, …
Constant : định nghĩa constant chứa các thông tin về tên Exchange, Queue.
Producer: để gửi Message đến Exchange.
Consumer: để nhận Message từ Queue.
App: giả lập việc gửi nhận Message thông qua Headers Exchange của RabbitMQ.
ConnectionManager.java
package com.gpcoder.headersexchange;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConnectionManager {
private ConnectionManager() {
super();
}
public static Connection createConnection() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
return factory.newConnection();
}
}
package com.gpcoder.headersexchange;
public final class Constant {
// Exchange
public static final String EXCHANGE_NAME = "GPCoderHeadersExchange";
// Queue
public static final String DEV_QUEUE_NAME = "QDeveloper";
public static final String MANAGER_QUEUE_NAME = "QManager";
public static final String PUBLISHED_QUEUE_NAME = "QPublished";
private Constant() {
super();
}
}
Producer.java
package com.gpcoder.headersexchange;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import static com.gpcoder.headersexchange.Constant.*;
public class Producer {
private HeadersExchangeChannel channel;
public void start() throws IOException, TimeoutException {
// Create connection
Connection connection = ConnectionManager.createConnection();
// Create channel
channel = new HeadersExchangeChannel(connection, EXCHANGE_NAME);
// Create headers exchange
channel.declareExchange();
// Create headers
Map<String, Object> devHeaders = new HashMap<>();
devHeaders.put("x-match", "any"); // Match any of the header
devHeaders.put("dev", "Developer Channel");
devHeaders.put("general", "General Channel");
Map<String, Object> managerHeaders = new HashMap<>();
managerHeaders.put("x-match", "any"); // Match any of the header
managerHeaders.put("dev", "Developer Channel");
managerHeaders.put("manager", "Manager Channel");
managerHeaders.put("general", "General Channel");
Map<String, Object> publishedHeaders = new HashMap<>();
publishedHeaders.put("x-match", "all"); // Match all of the header
publishedHeaders.put("general", "General Channel");
publishedHeaders.put("access", "publish");
// Create queues
channel.declareQueues(DEV_QUEUE_NAME, MANAGER_QUEUE_NAME, PUBLISHED_QUEUE_NAME);
// Binding queues with headers
channel.performQueueBinding(DEV_QUEUE_NAME, devHeaders);
channel.performQueueBinding(MANAGER_QUEUE_NAME, managerHeaders);
channel.performQueueBinding(PUBLISHED_QUEUE_NAME, publishedHeaders);
}
public void send(String message, Map<String, Object> headers) throws IOException {
// Send message
channel.publishMessage(message, headers);
}
}
Consumer.java
package com.gpcoder.headersexchange;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import static com.gpcoder.headersexchange.Constant.*;
public class Consumer {
private HeadersExchangeChannel channel;
public void start() throws IOException, TimeoutException {
// Create connection
Connection connection = ConnectionManager.createConnection();
// Create channel
channel = new HeadersExchangeChannel(connection, EXCHANGE_NAME);
// Create headers exchange
channel.declareExchange();
// Create headers
Map<String, Object> devHeaders = new HashMap<>();
devHeaders.put("x-match", "any"); // Match any of the header
devHeaders.put("dev", "Developer Channel");
devHeaders.put("general", "General Channel");
Map<String, Object> managerHeaders = new HashMap<>();
managerHeaders.put("x-match", "any"); // Match any of the header
managerHeaders.put("dev", "Developer Channel");
managerHeaders.put("manager", "Manager Channel");
managerHeaders.put("general", "General Channel");
Map<String, Object> publishedHeaders = new HashMap<>();
publishedHeaders.put("x-match", "all"); // Match all of the header
publishedHeaders.put("general", "General Channel");
publishedHeaders.put("access", "publish");
// Create queues
channel.declareQueues(DEV_QUEUE_NAME, MANAGER_QUEUE_NAME, PUBLISHED_QUEUE_NAME);
// Binding queues with headers
channel.performQueueBinding(DEV_QUEUE_NAME, devHeaders);
channel.performQueueBinding(MANAGER_QUEUE_NAME, managerHeaders);
channel.performQueueBinding(PUBLISHED_QUEUE_NAME, publishedHeaders);
}
public void subscribe() throws IOException {
// Subscribe message
channel.subscribeMessage(DEV_QUEUE_NAME);
channel.subscribeMessage(MANAGER_QUEUE_NAME);
channel.subscribeMessage(PUBLISHED_QUEUE_NAME);
}
}
App.java
package com.gpcoder.headersexchange;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
public class App {
public static void main(String[] args) throws IOException, TimeoutException {
// Create producers, queues and binding queues to Headers Exchange
Producer producer = new Producer();
producer.start();
// Publish some messages
Map<String, Object> devHeader = new HashMap<>();
devHeader.put("dev", "Developer Channel");
producer.send("[1] Developer message", devHeader);
Map<String, Object> managerHeader = new HashMap<>();
managerHeader.put("manager", "Manager Channel");
producer.send("[2] Manager message", managerHeader);
Map<String, Object> generalHeader = new HashMap<>();
generalHeader.put("general", "General Channel");
producer.send("[3] General message", generalHeader);
Map<String, Object> publishedHeader = new HashMap<>();
publishedHeader.put("general", "General Channel");
publishedHeader.put("access", "publish");
producer.send("[4] Published message", publishedHeader);
// Create consumers, queues and binding queues to Headers Exchange
Consumer consumer = new Consumer();
consumer.start();
consumer.subscribe();
}
}
developer có thể nhận bất kỳ Message nào có header là {“dev”, “Developer Channel”} hoặc {“general”, “General Channel”}.
manager có thể nhận bất kỳ Message nào có header là {“dev”, “Developer Channel”} hoặc {“general”, “General Channel”} hoặc {“manager”, “Manager Channel”}.
customer có thể nhận bất kỳ Message nào có header là {“dev”, “Developer Channel”} và {“access”, “publish”}.
Infrastructure as Code – xu hướng quản lý của doanh nghiệp
Trong kỷ nguyên công nghệ điện toán đám mây, doanh nghiệp bắt đầu phát triển, quản lý và vận hành cơ sở hạ tầng công nghệ với hệ thống máy chủ trên cloud, kho lưu trữ dữ liệu hoặc các ứng dụng IoT. Bên cạnh đó, doanh nghiệp cần phải liên tục ra mắt, nâng cấp các sản phẩm, dịch vụ trên thị trường với tốc độ nhanh chóng nhưng vẫn phải đảm bảo việc tối ưu số lượng nhân sự, giảm chi phí. Điều này tạo ra thách thức cho bộ phận IT về việc triển khai hệ thống nhanh với hạ tầng co giãn linh hoạt, đáp ứng quá trình vận hành, mở rộng và phát triển hạ tầng mới.
Sự ra đời của Infrastructure as Code đã giải quyết những khó khăn trên của doanh nghiệp bằng cách tự động hoá quá trình cấu hình cơ sở hạ tầng, giúp doanh nghiệp triển khai hệ thống nhanh nhất. Một trong những công cụ phổ biến nhất của Infrastructure as Code có thể nhắc đến là Terraform, một công cụ nguồn mở giúp xây dựng, chỉnh sửa và versioning cho cơ sở hạ tầng một cách an toàn và hiệu quả nhất.
Lợi ích của Terraform
Terraform vừa để quản lý các đơn vị cung cấp dịch vụ cloud như AWS, Azure, Google Cloud …, kể cả VNG Cloud và vừa được dùng trong nội bộ. Terraform quản lý đa dạng các hệ thống hạ tầng, từ compute instance, storage, networking đến DNS hay các giải pháp SaaS….
Với Terraform, doanh nghiệp có thể rút ngắn thời gian điều chỉnh, triển khai các hệ thống công nghệ, dễ dàng mở rộng quy mô máy chủ. Sử dụng Terraform còn giúp doanh nghiệp tăng khả năng giám sát toàn bộ hệ thống, đảm bảo an toàn cơ sở hạ tầng.
VNG Cloud là một trong những đơn vị đầu tiên tích hợp thành công Terraform tại Việt Nam, giúp doanh nghiệp có thể khởi tạo và quản lý network, server nhanh chóng và tối ưu nhất.
Hướng dẫn tích hợp Terraform vào hệ thống vServer
Chúng ta hãy tìm hiểu một ví dụ đơn giản, sử dụng Terraform để khởi tạo và quản lý đồng thời nhiều vServer cùng một lúc trên nền tảng VNG Cloud
Các bước cần thực hiện như sau:
Bước 1: Cài đặt Terraform bạn có thể theo hướng dẫn chi tiết tại đây, sau khi chọn đúng hướng dẫn cài theo hệ điều hành, bạn sẽ có thể gõ câu lệnh terraform –help để kiểm tra đã cài đặt thành công
Bước 2: Tải các example file với tên main.tf và variables.tf từ VNG Cloud repo hoặc bạn có thể tự viết những file này về một thư mục trên máy ví dụ như terraform-vngloud. Terraform sử dụng các file có đuôi .tf để bạn thiết lập các mô tả cho hạ tầng của mình,
main.tf: file này chứa các đoạn mã để tạo tài nguyên theo mong muốn, ví dụ đoạn mã dưới đây sẽ giúp Terraform khởi tạo vServer Instance:
variables.tf: file này chứa các biến được sử dụng trong file main.tf ở phía trên, bạn cần thay đổi các thông tin này ứng với nhu cầu của bạn, các thông tin như: cấu hình server, cấu hình disk, hệ điều hành, ssh key,…đồng thời bạn có thể chỉ định số lượng vServer cần khởi tạo ở biến server_count, ví dụ ở đây chúng ta chỉ định 10 vServer sẽ được khởi tạo cùng lúc.
variable “image_name” {
type = string
default = “1-Ubuntu-18.04×64”
}
variable “flavor_zone_name” {
type = string
default = “General v1 Instances”
}
variable “flavor_name” {
type = string
default = “v1.small1x1.b100”
}
variable “volume_type_name” {
type = string
default = “SSD-IOPS3000”
}
variable “root_disk_size” {
type = number
default = 20
}
variable “data_disk_size” {
type = number
default = 50
}
variable “server_count” {
type = number
default = 10
}
Bước 3: Sau quá trình cấu hình các thông tin ở tf file, bạn cần chạy lệnh terraform init để terraform khởi tạo và tải VNG Cloud provider về, đồng thời thiết lập các thông tin cần thiết
Bước 4: Chạy lệnh terraform plan để kiểm tra và hiển thị trước những tài nguyên sẽ được tạo hoặc có sự thay đổi
Bước 5: Chạy lệnh terraform apply để thực hiện việc khởi tạo vServer với những tf file đặc tả trên thông qua Terraform. Chúc mừng bạn khởi tạo thành công 10 vServer với Terraform
Để xem chi tiết hơn hướng dẫn này bạn có thể xem tại đây
Để giúp doanh nghiệp triển khai hệ thống, dịch vụ trên nền tảng VNG Cloud nhanh chóng bằng công cụ Terraform, doanh nghiệp/khách hàng vui lòng để lại thông tin tại đây, các chuyên gia của VNG Cloud sẽ liên hệ trong thời gian sớm nhất.
Trong các bài viết trước, chúng ta đã cùng tìm hiểu về Direct Exchange và Fanout Exchange. Trong bài này, tôi sẽ giới thiệu với các bạn một loại exchange khác là Topic Exchange.
Topic exchange (amq.topic) định tuyến message tới một hoặc nhiều queue dựa trên sự trùng khớp giữa routing key và pattern. Topic exchange được sử dụng để thực hiện định tuyến thông điệp multicast. Loại Exchange này thường được sử dụng để thực hiện các biến thể của Pub/Sub pattern.
Ví dụ một vài trường hợp sử dụng:
Phân phối dữ liệu liên quan đến vị trí địa lý cụ thể.
Xử lý tác vụ nền được thực hiện bởi nhiều workers, mỗi công việc có khả năng xử lý các nhóm tác vụ cụ thể.
Cập nhật tin tức liên quan đến một category hoặc gắn tag.
Điều phối các dịch vụ của các loại khác nhau trong cloud.
Một topic exchange sẽ sử dụng wildcard để gắn routing key với một routing pattern khai báo trong binding. Consumer có thể đăng ký những topic mà nó quan tâm.
Routing Key trong Topic Exchange:
Một Routing Key trong Topic Exchange phải bao gồm 0 hoặc nhiều từ phân cách bởi dấu chấm (.).
Routing Key trong Topic Exchange còn gọi là Routing Pattern.
Routing Pattern tương tự như Regular expression, nhưng chỉ các wildcard *, . và # được sử phép.
Ý nghĩa các wildcard được sử dụng là:
* : có nghĩa là chính xác một từ được phép.
# : có nghĩa là 0 hoặc nhiều số từ được phép.
. : có nghĩa là dấu phân cách từ. Nhiều từ chính được phân tách bằng dấu phân cách dấu chấm.
Ví dụ:
java.* : được đăng ký bởi tất cả những key với pattern bắt đầu bằng java và theo sau là chính xác một từ bất kỳ.
Những key sau là hợp lệ: java.core, java.gpcoder.
Những key sau là không hợp lệ: java, java.core.gpcoder.
java.*.gpcoder : được đăng ký bởi tất cả những key với pattern bắt đầu bằng java, theo sau là chính xác một từ bất kỳ và kết thúc là gpcoder.
Những key sau là hợp lệ: java.core.gpcoder, java.collection.gpcoder.
Những key sau là không hợp lệ: java.gpcoder, java.core.variable.gpcoder.
java.# : được đăng ký bởi tất cả các key bắt đầu với java.
Những key sau là hợp lệ: java, java.gpcoder, java.core.gpcoder.
Những key sau là không hợp lệ: core.java, core-java.com.
java.#.gpcoder : được đăng ký bởi tất cả những key với pattern bắt đầu bằng java và kết thúc là gpcoder.
Những key sau là hợp lệ: java.gpcoder, java.core.gpcoder, java.collection.map.gpcoder.
Những key sau là không hợp lệ: java.gpcoder.com, java.core.gpcoder.com.
#.gpcoder.com : được đăng ký bởi tất cả những key với pattern kết thúc là gpcoder.com.
Những key sau là hợp lệ: gpcoder.com, java.gpcoder.com, core.java.gpcoder.com.
Những key sau là không hợp lệ: java.gpcoder, gpcoder.com.java.
Flow của một Message trong Topic Exchange như sau:
Một Queue được tạo và binding tới một Topic Exchange với một routing key pattern (P).
Một Producer sẽ tạo một Message với một routing key (K) và publish tới Exchange.
Một Message được Exchange chuyển đến Queue nếu Pattern P match với Key K.
Consumer đăng ký tới Queue để nhận Message.
Ví dụ Topic Exchange trong RabbitMQ
Trong ví dụ này, tôi tạo một Topic Exchange có tên GPCoderTopicExchange, tạo 2 Queue binding tới Topic Exchange này:
QJava : Queue này sẽ nhận tất cả message có Key match với routing key “java.*.gpcoder.com“. Nghĩa là chỉ nhận các message cho một topic Java cụ thể từ gpcoder.com, chẳng hạn: java.core.gpcoder.com, java.collection.gpcoder.com.
QAll : Queue này nhận tất cả message có Key match với routing key “#.gpcoder.com“. Nghĩa là nhận tất cả message từ gpcoder.com, chẳng hạn: design-pattern.gpcoder, java.gpcoder.com, creational.design-pattern.gpcoder.com.
Một số class của chương trình:
ConnectionManager : hỗ trợ tạo Connection đến RabbitMQ.
TopicExchangeChannel : class util hỗ trợ tạo Echange, Queue, binding Queue đến Exchange, publish/ subscribe message, …
Constant : định nghĩa constant chứa các thông tin về tên Exchange, Queue.
Producer: để gửi Message đến Exchange.
Consumer: để nhận Message từ Queue.
App: giả lập việc gửi nhận Message thông qua Topic Exchange của RabbitMQ.
ConnectionManager.java
package com.gpcoder.topicexchange;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConnectionManager {
private ConnectionManager() {
super();
}
public static Connection createConnection() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
return factory.newConnection();
}
}
package com.gpcoder.topicexchange;
public final class Constant {
// Exchange
public static final String EXCHANGE_NAME = "GPCoderTopicExchange";
// Queue
public static final String JAVA_QUEUE_NAME = "QJava";
public static final String GENERAL_QUEUE_NAME = "QAll";
private Constant() {
super();
}
// Routing key pattern
public static final String JAVA_ROUTING_KEY = "java.*.gpcoder.com";
public static final String GPCODER_ROUTING_KEY = "#.gpcoder.com";
// Message key
public static final String JAVA_CORE_MSG_KEY = "java.core.gpcoder.com";
public static final String JAVA_MSG_KEY = "java.gpcoder.com";
public static final String DESIGN_PATTERN_MSG_KEY = "design-pattern.gpcoder.com";
public static final String NOT_MATCHING_MSG_KEY = "java.collection.gpcoder.com.vn";
}
package com.gpcoder.topicexchange;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import static com.gpcoder.topicexchange.Constant.*;
public class App {
public static void main(String[] args) throws IOException, TimeoutException {
// Create producers, queues and binding queues to Topic Exchange
Producer producer = new Producer();
producer.start();
// Publish some message
producer.send("[1] A new Java Core topic is published", JAVA_CORE_MSG_KEY);
producer.send("[2] A new Java general topic is published", JAVA_MSG_KEY);
producer.send("[3] A new Design Pattern topic is published", DESIGN_PATTERN_MSG_KEY);
producer.send("[4] Not matching any routing key", NOT_MATCHING_MSG_KEY);
// Create consumers, queues and binding queues to Topic Exchange
Consumer consumer = new Consumer();
consumer.start();
consumer.subscribe();
}
}
Output chương trình:
[Send] [java.core.gpcoder.com]: [1] A new Java Core topic is published
[Send] [java.gpcoder.com]: [2] A new Java general topic is published
[Send] [design-pattern.gpcoder.com]: [3] A new Design Pattern topic is published
[Send] [java.collection.gpcoder.com.vn]: [4] Not matching any routing key
[Received] [QJava]: amq.ctag-LiUyX8m4KIJu0Gb9WlpLdg
[Received] [QJava]: [1] A new Java Core topic is published
[Received] [QAll]: amq.ctag-nmlzYAixFEwB4P0-N6nltw
[Received] [QAll]: [1] A new Java Core topic is published
[Received] [QAll]: amq.ctag-nmlzYAixFEwB4P0-N6nltw
[Received] [QAll]: [2] A new Java general topic is published
[Received] [QAll]: amq.ctag-nmlzYAixFEwB4P0-N6nltw
[Received] [QAll]: [3] A new Design Pattern topic is published
Như bạn thấy:
Consumer binding đến Queue QJava chỉ nhận message được gởi từ message key “java.core.gpcoder.com”.
Consumer binding đến Queue QAll có thể nhận message gửi từ messag key “java.core.gpcoder.com”, “java.gpcoder.com” và “design-pattern.gpcoder.com”. Không nhận message từ “java.collection.gpcoder.com.vn” do message không phải gởi từ “gpcoder.com”.
Bài viết được sự cho phép của tác giả Nguyễn Hữu Khanh
Thông thường, chúng ta sẽ không sử dụng trang login mặc định của Spring Security cho các ứng dụng được deploy lên production, nguyên nhân vì sao các bạn cũng có thể đoán được đúng không? Trang login mặc định này để chúng ta learning là chủ yếu. Vậy để thay thế trang login mặc định này, chúng ta sẽ làm như thế nào? Trong bài viết này, mình sẽ hướng dẫn các bạn làm điều này.
Để đơn giản, custom page login của mình có các thành phần cũng giống như trang login mặc định của Spring Security, chỉ khác biệt là mình thay thế chữ “Please sign in” bằng dòng chữ “Welcome to Huong Dan Java, please login in” và nút “Sign in” giờ là “Login”.
Code trang login.html nằm trong thư mục src/main/resources/templates của mình với Bootstrap như sau:
Như các bạn thấy, từ đối tượng HttpSecurity, chúng ta sẽ gọi phương thức formLogin() để lấy về đối tượng FormLoginConfigurer để cấu hình cho trang login của Spring Security. Từ đối tượng FormLoginConfigurer này, chúng ta sẽ sử dụng phương thức loginPage() với tham số là tên của login page để để override lại trang login mặc định của Spring Security.
Mình cũng cấu hình thêm configure(WebSecurity web) để ignore security cho các đường dẫn liên quan đến WebJars, ở đây là đường dẫn đến Bootstrap CSS.
OK, đến đây thì các bạn có thể chạy lại ứng dụng để kiểm tra kết quả.
Trang login của mình giờ sẽ như sau:
Đăng nhập với username là user và password được generated, các bạn sẽ thấy kết quả như sau:
Bài viết được sự cho phép của blogchiasekienthuc.com
Chắc hẳn nhiều bạn ở đây đã biết về xác thực 2 yếu tố (2FA, xác thực 2 lớp hay còn gọi là Two-Factor Authentication) rồi đúng không !
Chắc chắn là như vậy rồi, nếu bạn đã sử dụng Internet, đã tham gia vào các MXH như Facebook, Instagram, hay các dịch vụ mạng và các tài khoản ngân hàng trực tuyến thì việc xác thực 2 yếu là điều bắt buộc trong việc bảo mật thông tin tài khoản.
Hiểu nôm na là khi bạn đăng nhập vào một website, hay đăng nhập một tài khoản/ ứng dụng trực tuyến thì ngoài việc nhập mật khẩu theo cách truyền thống ra thì sẽ có một mã xác thực OTP được gửi về Số điện thoại, Email hoặc là ứng dụng mà bạn đã thiết lập trước đó.
Như vậy, nếu chẳng may bạn có bị lộ mật khẩu thì người khác vẫn không thể đăng nhập vào được tài khoản của bạn được. Vì vãn còn một lớp bảo mật nữa mà 😀
Hiện nay, việc nhận mã OTP bằng điện thoại là phổ biến nhất, nhưng bạn thử nghĩ mà xem, lỡ điện thoại của bạn bị mất hoặc SIM của bạn bị lấy trộm/ bị hack thì kẻ gian sẽ có cơ hội để chiếm đoạt tài khoản của bạn. Có đúng không ạ !
Nhưng các bạn đừng quá lo lắng, bởi đa phần các trang web lớn, các trang TMĐT e-Commerce, các trang cần mức độ bảo mật cao hiện nay đa số đều đã hỗ trợ “Security key” làm phương thức xác thực 2 yếu tố (có thể làm yếu tố chính hoặc dự phòng cho phương pháp SMS hoặc ứng dụng xác thực).
Vâng, và ở trong bài viết này mời các bạn hãy cùng mình tìm hiểu về “Security key” – “Chiếc khóa thần kỳ” này nhé, để xem cách thức hoạt động và mức độ bảo mật của nó ra sao ha.
#1. Security key là gì?
Nói một cách dân dã, đơn giản và dễ hiểu thì Security key là một chiếc chìa khóa. Hình dạng của chiếc chìa khóa này nó cũng không khác gì một chiếc USB Drive để cắm vào cổng USB của máy tính là mấy.
Security key chứa thông tin mã hóa nhằm chứng minh bạn là chủ của tài khoản, cơ chế hoạt động như ổ khóa và chìa khóa vậy.
Sau khi bạn cấu hình Security key vào tài khoản thì trang web là “ổ khoá” còn Security key này sẽ là chiếc “chìa khóa” để mở quyền truy cập vào tài khoản.
Có nghĩa là, sau khi bạn đăng nhập bằng mật khẩu như bình thường thì bước tiếp theo, trang web sẽ yêu cầu bạn cắm Security key vào máy để tiến hành xác thực bạn có quyền truy cập vào tài khoản này.
Các thương hiệu Security key phổ biến hiện nay như: YubiKey, HyperFIDO, Thetis, Google Titan… với các chuẩn cắm USB Type-A, USB Type-C hay kết nối qua Bluetooth, xác thực dùng chuẩn FIDO U2F (FIDO Universal 2nd Factor authentication) là chính, ngoài ra còn có chuẩn Fido2 cải tiến nữa.
Khóa bảo mật này có thể coi như là chiếc chìa khóa xe, chiếc chìa khóa duy nhất để vào nhà bạn => vậy nên bạn cần phải giữ thật kỹ, tốt nhất là móc vào chìa khóa xe.
Lưu ý quan trọng là khóa này không thể mang ra thợ đúc sơ-cua nha các bạn, mình xác nhận là mấy ông thợ sửa khóa đầu ngõ bất lực với trường hợp này nhé ᵔᴥᵔ
Cơ chế hoạt động của Security key thì khá là đơn giản:
Sau khi mở trang web cần truy cập => bạn cắm Security key vào thiết bị mà bạn sử dụng (smartphone hoặc máy tính) hoặc sử dụng kết nối không dây => rồi bấm nút trên Security key => lúc này trình duyệt web (Browser)/ ứng dụng sẽ gửi lệnh truy cập (bao gồm tên miền/domain name) đến Security key.
=> Sau đó key này sẽ ký số(Cryptographically sign) và cho phép bạn truy cập vào, như vậy là bạn đã hoàn thành bước xức thực 2 yếu tố rồi đó.
Các “ông lớn” đang hỗ trợ đăng nhập bằng Security Key như: Twitter, Facebook, Google, Instagram, GitHub, Dropbox, Electronic Arts, Epic Games, Microsoft, Nintendo, Okta, Reddit…
Thậm chí là bạn có thể đăng nhập vào máy Mac OS bằng Security key. Microsoft cũng đang phát triển/ nâng cấp Windows Hello để đăng nhập bằng key dùng chuẩn Fido2 được hiệu quả hơn, chính xác hơn.
#2. Thiết lập Security key với Facebook
Trong bài viết này mình sẽ demo với các bạn các bước thiết lập xác thực 2 yếu tố bằng Security key. Các dịch vụ khác hoàn toàn tương tự vậy thôi nhé các bạn !
+ Bước 1: Đăng nhập vào tài khoản Facebook của bạn => rồi click vào menu bên góc phải => và chọn Settings (Cài đặt).
+ Bước 2: Trong phần Bảo mật => bạn chọn Security and Login (Bảo mật và đăng nhập) như hình bên dưới..
+ Bước 3: Bạn cuộn xuống phần Two-Factor Authentication (Xác thực 2 yếu tố).
+ Bước 4: Sau đó, tại phần Two-Factor Authentication (Xác thực 2 yếu tố) => bạn cuộn xuống và tìm đến phần Add a Backup (Thêm phương thức dự phòng) => và chọnSecurity Key (Khóa bảo mật) như hình bên dưới.
+ Bước 5: Bây giờ bạn hãy nhập mật khẩu tài khoản Facebook của bạn vào => và chọn Submit.
Tiếp theo, cắm Security key vào cổng kết nối trên thiết bị => rồi ấn nút trên thân của khóa => lúc này trang Facebook sẽ hiện lên hộp thoại xác nhận và sau này bạn có thể đăng nhập bằng key này..
_______________________
Đối với Facebook giao diện trên máy tính thì bạn chỉ cần truy cập vào địa chỉ này:
https://www.facebook.com/security/2fac/settings
=> Sau đó trong phần Thêm phương pháp dự phòng => bạn chọn phương pháp Khóa bảo mật như hình bên dưới.
Sau đó bạn nhập mật khẩu và để xác nhận.
Bây giờ bạn hãy kết nối khóa bảo mật USB vào và thực hiện theo hướng dẫn của Facebook..
Quy trình cài đặt, thiết lập Security key để xác thực 2 bước cho tài khoản Google hay Twitter,… đều tương tự như trên, các bạn không rành tiếng Anh thì nên tận dụng tính năng dịch trên trình duyệt web nhé bạn.
#3. Tổng kết
Okay, qua bài viết này thì mình tin bạn đã hiểu hơn về hình thức Security key rồi đúng không. Và qua bài viết này thì mình cũng tin rằng bạn có thể tự thiết lập khóa bảo mật (Security key) làm bước xác thực 2 yếu tố dự phòng rồi.
Nói chung, nguyên tắc của khóa Security key tương tự như một chiếc chìa khóa ngoài đời thực của bạn vậy. Bạn hãy giữ khóa này cẩn thận, nếu làm mất thì phải truy cập ngay vào các tài khoản để xóa Security key đi nhé, đồng thời mua “khóa” mới và thêm vào ngay.
Security key hỗ trợ bảo mật rất cao nhưng có vẻ hơi phức tạp đối với người dùng phổ thông, các bạn có thể tìm hiểu giải pháp dự phòng khác đối với tài khoản của mình như: Mã dự phòng hay là SMS OTP, ứng dụng xác thực 2 yếu tố..
Okay, hi vọng là bài viết này sẽ hữu ích với bạn, chúc các bạn thành công và đừng quên chia sẻ bài viết này nếu bạn thấy thích nhé 🙂