Thứ Năm, 4 tháng 8, 2016

Xây dựng ứng dụng web sử dụng MVC2 kết hợp JavaEE6

Xây dựng ứng dụng web sử dụng MVC2 kết hợp JavaEE6

Mục đích: Chủ đề của bài này đề cập đến việc xây dựng ứng dụng web sử dụng mô hình thiết kế MVC2 với các chức năng cơ bản kết nối DB sử dụng JavaEE 6. Bên cạnh đó, bài cũng giới thiệu các kỹ thuật để cài đặt ứng dụng theo dạng module và component để dễ dàng chỉnh sửa và thay đổi khi mở rộng và nâng cấp

Xây dựng ứng dụng web sử dụng MVC2 kết hợp JavaEE6

Mục đích: Chủ đề của bài này đề cập đến việc xây dựng ứng dụng web sử dụng mô hình thiết kế MVC2 với các chức năng cơ bản kết nối DB sử dụng JavaEE 6. Bên cạnh đó, bài cũng giới thiệu các kỹ thuật để cài đặt ứng dụng theo dạng module và component để dễ dàng chỉnh sửa và thay đổi khi mở rộng và nâng cấp

Yêu cầu

Một số khái niệm mới trong JavaEE6

  • Trong JavaEE6, tập tin web.xml là một tập tin có hay không cũng được bởi vì các annotation được sử dụng để thay thế việc khai báo phức tạp của tập tin này nhằm đơn giản hóa cho người dùng. Đây là cách thức JavaEE6 tạo ra các cấu hình động sẽ được phát sinh tại thời điểm deploy và quá trình ứng dụng đang thực thi
  • Tuy nhiên, mặc định trong project JavaEE6, tập tin mặc định chạy đầu tiên là tập tin index.jsp. Do vậy, nếu chúng ta xây dựng ứng dụng theo mô hình MVC thì trong tập tin index.jsp, chúng ta phải sử dụng dispatching mechanism của JSP với standard action <jsp:forward> để chuyển sang Controller chính của hệ thống.  
  • Để giảm bớt sự rắc rối và không thuận lợi nêu trên, chúng tôi khuyến cáo vẫn sử dụng vừa tập tin web.xml để cấu hình trang hay resource chạy khởi đầu và mapping controller chính của hệ thống kết hợp với annotation để thiết lập cho các resource khác tạo sự thuận lợi trong cấu hình về thiết lập và thuận lợi trong việc lập trình ngắn gọn và xúc tích
  • Các annotation phổ biến thường được sử dụng cho servlet bao gồm
    • @WebServlet (name=”tên Servlet”, urlPatterns=”url dùng để truy cập servlet”)
    • Nội dung của một annotation trên thay thế cho 02 thẻ <servlet></servlet>
      và <servlet-mapping></servlet-mapping> trong tập tin web.xml

RequestDispatcher

  • Ở đây chúng tôi đề cập lại một chút khái niệm của RequestDispatcher bởi vì đây là nội dung chính yếu sẽ được sử dụng trong mô hình MVC để hướng tới việc chia module hay component trong lúc lập trình và bảo trì phần mềm. Đây cũng là một cơ chế hướng tới việc chia tải ở phía server và chia nhỏ công việc trong lúc xây dựng ứng dụng theo hướng OOP với 02 đặc tính high cohensive và low coupling và modularity
  • Đầu tiên chúng tôi đề cập đến mô hình hoạt động của response.sendRedirect
    • Cơ chế này giúp chúng ta chuyển resource trong server. Tuy nhiên, khi cơ chế này được thực hiện thì response đã được chuyển và tạo ra một request mới để yêu cầu resource khác ở server. Hiểu theo một cách khác, thì request object đã kết thúc quá trình làm việc của nó và bị hủy đi. Điều này dẫn đến dữ liệu cần để giao tiếp giữa các resource không còn lưu trữ. Nếu để dữ liệu trên tầm vực session thì sẽ gây nên phí phạm bộ nhớ và một số thao tác không cần thiết để hủy phần lưu trữ này
    • Bên dưới là mô hình hóa của cơ chế sendRedirect

  • RequestDispatching với forward là một phương pháp đề xuất với các khả năng như sau
    • Forward hay đưa toàn bộ request từ resource này sang resource khác trên server và đối tượng resource cuối cùng sẽ response. Điều này có nghĩa là request object sẽ được duy trì trong suốt thời gian này để lưu trữ thêm thông tin
    • Khi sử dụng forward, chúng ta sẽ chỉ thấy được tên resource đầu tiên được yêu cầu trên thanh url của browser. Điều này, có nghĩa là nó hỗ trợ việc che dấu thông tin xử lý của các resource khác. Nghĩa là hỗ trợ thuận lợi trong việc mapping linh hoạt trong xây dựng ứng dụng
    • Chính điều này dẫn tới nhằm giảm tải cho ứng dụng MVC nếu controller đón nhận tất cả yêu cầu rồi xử lý. Điều này dẫn đến tình trạng nghẽn vì cho dù có nhiều thread được hỗ trợ trong container nhưng tại một thời điểm chỉ xử lý được một chức năng dẫn đến các chức năng khác cũng bị block. Nhìn ở khía cạnh lập trình, thì việc chia sẻ công việc là không khả thi vì các chức năng tập trung ở một chỗ.
    • Với cơ chế này, chúng ta sẽ xây dựng mỗi servlet là một chức năng và một servlet đóng vai trò controller để điều phối và định hướng theo dạng chức năng nào sẽ do servlet nào đó xử lý mà người sử dụng không cần quan tâm cách xử lý và quan tâm kết quả. Thông qua requestDispatcher.forward chúng ta đã xây dựng một khái niệm điều phối, đón nhận yêu cầu nhưng không xử lý và chuyển xử lý đến chức năng và response cho người dùng. Người dùng đón nhận kết quả và cảm nhận rằng kết quả là do servlet trung tâm xử lý. Điều này tạo thuận lợi cho việc làm việc nhóm, mapping code và dễ dàng bảo trì thay thế cho phần mềm và đạu chuẩn MVC cho ứng dụng
    • Mô hình hóa của cơ chế forward của RequestDispatcher

Servlet trong ứng dụng

  • Để có thể cấu hình và tích hợp code trong quá trình làm việc nhóm, chúng tôi đưa ra các qui tắc như sau
    • Mỗi Servlet chỉ nên thực hiện duy nhất một chức năng để đảm bảo tính high cohensive và low coupling trong tích hợp và tái sử dụng ứng dụng
    • Luôn có một Servlet làm trung tâm đóng vai trò điều phối và chuyển tải và không làm nghẽn ứng dụng khi có một số lương lớn yêu cầu xuất hiện. Điều này chính là ứng dụng request Dispatcher trong servlet
    • Servlet là nơi điều phối, không phải là nơi xử lý code. Điều đó có nghĩa là việc yêu cầu chức năng phải nằm trên đối tượng BLO – Business Logic Object hay DAO – Data Access Object. Điều này giúp ứng dụng dễ thay đổi và thích nghi với mọi sự thay đổi khi tương tác với dữ liệu
    • Để hỗ trợ cấu hình thuận tiện cho việc khới tạo ứng dụng và đưa trang mặc định đến người dùng, chúng tôi sẽ dụng kết hợp giữa web.xml và annotation với khái niệm cấu hình khởi tạo resource đầu tiên khi người dùng yêu cầu hệ thống và servlet trung tâm sẽ được cấu hình trong web.xml để mapping trung tâm đầu tiên. Các servlet xử lý thì sử dụng annotation

DataSource

  • Là tên luận lý hay cái nhãn để người sử dụng có thể kết nối database mà không cần biết database ở đâu, sử dụng DBMS nào, port hay security là gì
  • Mỗi nhãn hay tên luận lý sẽ được cấu hình với địa chỉ IP, port, tên DB, security để truy cập DB
  • Đây là cơ chế thuận lợi để hỗ trợ ứng dụng kết nối với tất cả DB trong qua API dùng chung và sự thay đổi không làm ảnh hưởng đến quá trình phát triển phần mềm hay cụ thể hơn là không cần thay đổi code khi có sự thay đổi dữ liệu

Vận dụng các khái niệm trên kết hợp mô hình MVC + JavaEE 6, chia ứng dụng thành các component vào việc phân tích và cài đặt một ứng dụng cụ thể

  • Ứng dụng ở đây mô tả việc xây dựng một ứng dụng cung cấp chức năng như sau
    • Ngưởi dùng muốn truy cập vào hệ thống phải thực hiện login để kiểm tra username và password có hợp lệ hay không
    • Hệ thống thông qua DB kiểm tra tính xác thực của dữ liệu
      • Nếu username và password không chính xác hay không tồn tại ứng dụng thông báo cho người dùng thông tin “Invalid username and password” và cho người dùng trở về trang Login thông qua một Link có tên là try again và cung cấp một Link Register cho phép người dùng đăng ký một account mới
      • Việc đăng ký account mới sẽ phải tuân thủ ràng buộc về nhập dữ liệu
      • Nếu user tồn tại thì chương trình bày form Search. Đặc biệt, trên đầu trang phải sử dụng session để lưu trữ user và tất cả các trang phải có câu “Welcome, tênUser”
    • Form Search cho phép người dùng tìm kiếm một user bất kỳ khi biết một phần tên của họ
      • Kết quả Search sẽ trình bày trên lưới dữ liệu
      • Nếu tìm không thấy sẽ in ra câu “No Result is matched!”
    • Kết quả trên lưới dữ liệu cho phép người dùng xóa một hàng bất kỳ bằng cách click vào link hay update thông tin về lastname hay roles bằng click nút update trên hàng được lựa chọn
    • Khi các thao tác update, delete được thực hiện thành công thì lưới dữ liệu sẽ được cập nhật lại và trình bày kết quả cho người dùng
    • Ngoài ra, ứng dụng còn hỗ trợ người dùng trong việc mua sách và quản lý giỏ hàng trong quá trình họ mua
  • Mô tả yêu cầu của bài trên thể hiện đầy đủ chức năng cơ bản của một ứng dụng thực tế và kết nối DB. Chúng tôi sẽ phân tích và làm bài theo từng chức năng và áp dụng mô hình MVC từng chức năng một từng bước từng bước một.
  • DB chúng tôi thao tác có dạng như sau

  • Yêu cầu
    • Nắm vững các kiến thức đã nêu trên, nắm vững khái niệm RequestDispatcher
    • Nắm vững các khái niệm về MVC
    • Nắm vững về ngôn ngữ lập trình Java, lập trình thao tác hướng đối tượng
    • Cách thức sử dụng JSTL, EL, HTML
    • Tools sử dụng ở đây là Netbeans 8.1, 7.4
    • JDK 8 update 66 hay JDK 7 update 51 – khuyến cáo sử dụng JDK 7 là ổn định nhất
    • Server: Tomcat 8.0.27, 7.0.41
    • DBMS: SQL Server 2005, 2008, 2012, 2014
    • Thư viện hỗ trợ: JSTL 1.1, 1.2.1, 1.2.2; Driver kết nối database SQLServer (sqljdbc4.jar)
  • Các bước thực hiện
    • Tạo Web Application
      • Tên project: MVC2JavaEE6
      • Server: Apache Tomcat 8.0.27
      • J2EE version: JavaEE 6 Web
      • Các bước thực hiện
      • New Project

Chọn Categories Java Web, chọn Projects Web Application. Nhấn Next

Đặt tên ứng dụng. Nhấn Next

Chọn Server Tomcat. Chọn JavaEE version Java EE 6 Web. Nhấn Next

Không chọn gì cả. Nhấn Finish.

      • Cấu trúc project trên Netbeans sau khi được tạo  như sau

    • Chúng ta mô hình hóa chức năng Login để dễ dàng theo dõi khi lập trình

      • Bước đầu tiên, chúng ta thực hiện tạo các trang hiển thị và yêu cầu nhập liệu ở phía Client
      • Bắt đầu với việc tạo trang login.html trong web page

      • Tạo trang invalid.html trong web page

      • Tạo trang search.jsp trong web page

      • Tạo ProcessServlet là một servlet đóng vai trò servlet trung tâm để thực hiện điều phối. Như đã đề cập, servlet này được cấu hình trong tập tin web.xml
      • Tạo Servlet, đặt tên là ProcessServlet. Đặt toàn bộ servlet trong package sample.servlet (package name tùy ý quí vị đặt tên). Nhấn Next

      • Để hỗ trợ khai báo trong trang web.xml, click chọn Add information to Deployment Descriptor (web.xml). Nhấn Finish

      • Tập tin web.xml được tự động phát sinh và chúng ta sửa lại giá trị của welcome-file là ProcessServlet

      • Chúng ta thực hiện viết code cho servlet này áp dụng RequestDispatcher để chuyển xử lý cho LoginServlet. Lưu ý xử lý cho trường hợp parameter khi servlet này được gọi trực tiếp là null. Nếu giá trị được xác định là null thì chúng ta xử lý luôn luôn chuyển sang trang mặc định là Login

      • Tạo LoginServlet trong gói package sample.servlet với điểm khác biệt là không chọn Add information to Deployment Descriptor (web.xml) để áp dụng annotation của JavaEE 6 dành cho servlet

      • LoginServlet được phát sinh với các annotation và không có gì được khai báo trong web.xml

      • Để thực hiện login, chúng ta phải kết nối Database. Để làm được điều này chúng ta cần phải cấu hình DataSource
        • Lưu ý: add thư viện driver hỗ trợ kết nối SQLServer (sqljdbc.jar, sqljdbc4.jar) vào library hay chép thẳng driver kết nối vào thư mục cài đặt Tomcat (TOMCAT_HOME\lib – và start lại Tomcat để thư viện này được nạp vào server để sử dụng)

        • Mở tập tin context.xml trong thư mục META-INF

        • Cấu hình DataSource với ip kết nối DB, tên DB và security truy cập DB. Ở đây, chúng ta sẽ giao toàn quyền quản lý cho Container

        • Sau khi cấu hình xong, chúng ta phải reference resource này vào ứng dụng web. Chúng ta mở tâp tin web.xml, click chọn nút Reference

        • Click nút Add tại Resource References

        • Điền tên DataSource đã cấu hình trong phần name của tập tin context.xml vào và nhấn OK

        • Code được phát sinh trong tập tin web.xml như sau

        • Chúng ta tiếp tục xây dựng thư viện để hỗ trợ kết nối đến DB. Tạo JavaClass với package sample.utils để lookup Connection để đưa cho ứng dụng sử dụng

        • Chúng ta vừa hoàn tất cách cấu hình DataSource để kết nối DB
      • Tạo DAO – Data Access Object được dùng để hỗ trợ các chức năng mapping dữ liệu từ DB để xử lý.
      • Lưu ý : mỗi table trong DB nên và chỉ nên có một DAO duy nhất. Để dễ quản lý, các DAO thường có tên là Tên_BảngDAO và package chính là tên của table

        • Code được implement như sau

        • Chúng ta vừa hoàn thành xong việc tạo mapping ứng dụng với DB
      • Chúng ta quay lại LoginServlet để hoàn tất code login

      • Cấu trúc project chúng ta như sau

      • Start Server Tomcat, Clean And Build Project, Deploy và Test

 

        • Nếu check sai

 

        • Lưu ý: chúng ta thấy trên Address bar không có tên file vì đang áp dụng RequestDispathcher như đã đề cập trong phần Servlet – Controller
      • Chúng ta vừa thực hiện hoàn tất chức năng Login
    • Chúng ta tiếp tục thực hiện chức năng Search cho hệ thống
      • Chúng ta mô hình hóa chức năng Search như sau

      • Ở trong mô hình náy, chúng ta nhìn thấy, đối tượng DTO – Data Transfer Object được sử dụng để đón nhận giá trị mapping từ DB và chuyển sang resource để duyệt và trình bày ra giao diện người dùng. Đối tượng này được sử dụng vì kết quả search trả về nhiều hơn một dòng dữ liệu
      • Chúng ta mapping việc xử lý chức năng Search thông qua ProcessServlet. Từ ProcessServlet, SearchServlet sẽ đảm nhận việc xử lý

      • Chúng ta phát sinh SearchServlet và sử dụng annotation khi khai báo

      • Chúng ta tạo đối tượng DTO mapping với các cột trong DB để chứa dữ liệu sau khi truy vấn dưới DB làm nền cho việc duyệt dữ liệu và in ra màn hình
      • Chúng ta tạo đối tượng DTO cùng package với gói DAO với các đặt tên tên_BảngDTO

      • Nội dung của DTO bao gồm : các thuộc tính mapping dưới DB không được là public, định nghĩa các phương thức get và set cho chúng ; và một constructor không tham số

      • Chúng ta thực hiện bổ sung phương thức search vào trong DAO
        • Chúng ta cần khai báo List của DTO để đón nhận dữ liệu
        • Thực hiện việc truy vấn search dưới DB và đổ dữ liệu vào trong List

      • Cập nhật SearchServlet để chuyển sang trang search.jsp để trình bày dữ liệu ngay bên dưới của control search

      • Thực hiện duyệt dữ liệu trên trang search.jsp
        • Tại đây, chúng ta cần thư viện JSTL hỗ trợ trình bày giao diện. Add thư viện này vào trong project

 

      • Cấu trúc project đến lúc này

      • Cập nhật lại trang search.jsp

      • Build, Deploy và Testing Project

        • Click nút Search

        • Chúng ta vừa hoàn tất xong chức năng Search
    • Chúng ta tiếp tục thực hiện với chức năng Delete
      • Kỹ thuật được áp dụng ở đây chính là kỹ thuật URL Rewriting để truyền giá trị về phía server nhằm xử lý delete đồng thời lưu lại giá trị search lần cuối cùng
      • Phía server, sau khi xử lý xong chúng ta cần áp dụng kỹ thuật URL Rewriting một lần nữa để gọi lại chức năng Search nhằm refresh lưới ở phía server
      • Mô hình hóa chức năng delete và update như sai

      • Chúng ta thực hiện cập nhật trang search.jsp để hỗ trợ link Delete trên lưới kết quả search

      • Thực hiện cập nhật ProcessServlet để chuyển xử lý đến DeleteServlet

      • Tạo DeleteServlet sử dụng annotation

      • Cập nhật DAO cho chức năng Delete

      • Cập nhật DeleteServlet

      • Tạo trang deleteErr.html

      • Cấu trúc ứng dụng hiện nay như sau

      • Thực hiện Clean and Build, Deploy và Test Project

        • Click Delete

        • Chúng ta vừa hoàn tất chức năng Delete
    • Chúng ta tiếp tục xây dựng chức năng Update
      • Mô hình hóa của chức năng này giống như là Delete ngoại trừ để đón nhận được nội dung chỉnh sửa của người dùng, chúng ta sẽ áp dụng kỹ thuật Hidden Form Field
      • Chúng ta thực hiện cập nhật trang search.jsp để bổ sung các giá trị cần gửi server cập nhật, lần cuối cùng search, các giá trị ẩn

      • Thực hiện bổ sung chuyển xử lý trong ProcessServlet để đi tới UpdateServlet

      • Tạo UpdateServlet sử dụng annotation

      • Cập nhật DAO với chức năng Update

      • Cập nhật code hoàn tất chức năng trên UpdateServlet

      • Tạo trang updateErr.html hỗ trợ báo lỗi khi update không thành công

      • Cấu trúc ứng dụng đến hiện tại

      • Thực hiện Clean and Build, Deploy, và Test Ứng dụng
        • Thực hiện các thao tác đến form Search

        • Thay đổi giá trị và nhấn Update
      • Chúng ta vừa hoàn thành xong chức năng Update
    • Chúng ta tiếp tục với chức năng tạo ra Account mới cho hệ thống. Đây là mô hình hóa cho chức năng insert dữ liệu xuống DB
      • Chúng ta mô hình hóa chức năng này bao gồm việc kiểm tra lỗi
      • Khi nào không còn lỗi nào thì dữ liệu mới được insert xuống DB và trang login được hiển thị cho người sử dụng
      • Nếu lỗi xảy ra, lỗi sẽ trình bày trực tiếp ngay trên trang người dùng đang nhập liệu

      • Ở đây, để tăng tính năng hiệu quả cho hệ thống, chúng tôi xây dựng 02 trang đăng ký,
        • 01 trang html để cho người dùng nhập liệu ban đầu (html load nhanh mà không cần convert)

        • 01 trang jsp để xử lý và trình bày lỗi tương tự html (jsp phải được convert thành servlet và output thành html)
      • Bổ sung code trong ProcessServlet để chuyển sang CreateAccountServlet

      • Tạo CreateAccountServlet với annotation

      • Bổ sung chức năng insert vào DAO

      • Để hỗ trợ xử lý lỗi, chúng ta sẽ tạo ra đối tượng đón nhận lỗi insert trong package table với tên RegistrationInsertErr

           

      • Cập nhật code xử lý lỗi và chức năng insert vào CreateAccountServlet

      • Bổ sung code xử lý trình bày lỗi trên trang createNewAccount.jsp

      • Thực hiện mapping trang html để tạo account mới vào trang login.html và invalid.html

      • Cấu trúc project đến hiện hành

      • Clean and Build, Deploy và Test Project

        • Click Link Click here to Sign Up

 

      • Nhập các trường hợp sai theo tuần tự

 

 

 

        • Chúng ta vừa hoàn tất xong chức năng insert dữ liệu và bắt lỗi trực tiếp
    • Chúng ta hoàn tất chức năng cuối cùng với việc mua sách trực tiếp
      • Chức năng này bao gồm 03 chức năng nhỏ nên trong đó là Add To Cart, View Cart và Remove Cart. Ở đây, chúng tôi không thực hiện chức năng check out vì đây cũng chính là hành động insert, quí vị có thể tự làm
      • Chúng ta bắt đầu với chức năng add To Cart
        • Chúng ta cần có đối tượng để chứa đựng hàng đi chợ của người dùng
        • Chúng ta sử dụng session để lưu trữ giỏ hàng của người dùng trong thời gian dài để mua hàng, bỏ bớt hàng hay check out
        • Mô hình hóa của chức năng Add To Cart như sau

        • Chúng ta tạo trang shopping.html để người dùng lựa chọn sách, view cart

        • Chúng ta tiếp tục cập nhật ProcessServlet để chuyển xử lý đến AddItemServlet

        • Chúng ta xây dựng đối tượng Cart để chứa hàng cho người dùng trong quá trình ứng dụng đang thực thi với 02 hành vi thêm hàng vào giỏ và bỏ hàng ra khỏi giỏ

        • Tạo AddItemServlet sử dụng annotation và hoàn tất code

        • Cấu trúc ứng dụng hiện nay

        • Chúng ta vừa hoàn thành chức năng Add To Cart. Tuy nhiên, để kiểm tra được, chúng ta cần phải thực hiện chức năng View Cart
      • Chúng ta thực hiện chức năng View Cart với mô hình hóa như sau

        • Bổ sung việc chuyển xử lý từ ProcessServlet đến trang viewCart.jsp

        • Tạo trang viewCart.jsp trong web Page và bổ sung code

        • Chúng ta add link vào trang login.html

        • Clean và Build, deploy và test project

        • Click Link Click here to buy books

        • Thực hiện chọn các sách và Add Book to Your Cart hàng loạt rồi nhấn nút View Your Cart

        • Chúng ta vừa hoàn tất xong chức năng View Cart
      • Chúng ta thực hiện tiếp tục với chức năng Remove các món hàng được lựa chọn ra khỏi giỏ hàng
        • Chúng ta áp dụng kỹ thuật hidden form field với dạng giá trị được ẩn trong một control khác, ở đây chính là checkbox
        • Các checkbox trùng trên nhau sẽ được hình thành một mảng giá trị được lựa chọn
        • Khi xử lý xóa xong ở server, để cập nhật giao diện ở client, chúng ta sử dụng cơ chế URL Rewriting để gọi lại chức năng view một lần nữa
        • Mô hình hóa của chức năng remove các món hàng được lựa chọn như sau

        • Chúng ta thực hiện cập nhật giao diện cho trang viewCart.jsp để có các control và form để gứi về server

        • Cập nhật chức năng xử lý cho ProcessServlet chuyển tới RemoveItemServlet để xử lý

        • Tạo RemoveItemServlet sử dụng annotation và cập nhật code để xử lý

        • Cấu trúc ứng dụng đến chức năng này như sau

        • Clean And Build, Deploy và test Project

          • Click Add more cart để thêm hàng

          • Check vào các item lựa chọn

        • Nhấn nút Remove Selected Items

Chúc mừng các bạn đã hoàn tất một ứng dụng web site với đầy đủ các chức năng cơ bản. Bên cạnh đó, chúng ta đã ứng dụng mô hình MVC2 kết hợp JavaEE 6 với các annotation và cách phân chia module trong quá trình lập trình nhẳm tạo sự uyển chuyển cho ứng dụng khi mở rộng và triển khai

Chúng tôi hy vọng nội dung của bài này giúp ích các bạn

Rất mong sự góp ý chân thành và chia sẻ của quí vị về vấn đề này. Hẹn gặp lại quý vị ở chủ đề khác

Không có nhận xét nào:

Đăng nhận xét