Khi làm việc với các dự án lớn liên quan đến các hệ thống thời gian thực, việc theo dõi và báo cáo tình trạng của hệ thống là vô cùng quan trọng. Việc theo dõi và gửi các thông số cho quản trị viên nắm được tình hình hệ thống tốt nhất mà không cần can thiệp và phân tích log trên server.

 

 

Phải xác định rằng, thành phần theo dõi và gửi báo cáo sẽ hoạt động như là một module độc lập tách rời khỏi hệ thống. Chúng ta có 2 cách:

  • Cung cấp các service để hệ thống chính ghi lại các thông số trong quá trình hoạt động. (Cách này khi implement thì sẽ thay đổi cấu trúc module chính nếu thay đổi cơ chế theo dõi từ module monitor).
  • Thông qua công nghệ Spring AOP, cho phép module monitor tự gắn kết và module chính mà không làm thay đổi cấu trúc của nó bằng cách lắng nghe theo class hoặc bắt các luồng dữ liệu được xử lý trong module đó.

Vì đây là hướng dẫn ban đầu nên mình chỉ đơn giản làm theo cách 1.

Bên cạnh việc theo dõi, chúng ta sẽ có các job cài đặt sẵn cho mục đích báo cáo như hằng ngày, hằng tuần…

 

 

TẢI VỀ MÃ NGUỒN

Mã nguồn đầy đủ project này tại đây: https://github.com/vodailuong/sys-monitor

 

CÔNG NGHỆ SỬ DỤNG

           

  • Chúng ta sẽ sử dụng Maven, kiến trúc Spring MVC 4, deploy trên jBoss Server 7.1
  • Quartz Scheduler để tạo các job chạy tự động theo thời gian thực
  • Dùng JavaMail Thymeleaf để cấu hình và định dạng email gửi đi

 

KHỞI TẠO MAVEN PROJECT, TÍCH HỢP SPRING MVC VÀ CÁC THÀNH PHẦN CẦN THIẾT

Như đã nói chúng ta sử dụng maven, tạo 1 project maven từ eclipse.

 

Mở file pom.xml và add vào các dependency cần thiết:

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <springframework.version>4.1.2.RELEASE</springframework.version>
        <log4j.version>1.2.17</log4j.version>
        <quartz-scheduler.version>2.2.1</quartz-scheduler.version>
        <mail.version>1.4.7</mail.version>
        <thymeleaf.version>2.1.3.RELEASE</thymeleaf.version>
        
        <jboss.version>7.1.1.Final</jboss.version>
        <jboss-jms.version>1.0.0.Final</jboss-jms.version>
    </properties>

    <dependencies>
        <!-- spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${springframework.version}</version>
        </dependency>

        <!-- Java Mail API -->
        <dependency>
            <groupId>javax.mail</groupId>
            <artifactId>mail</artifactId>
            <version>${mail.version}</version>
        </dependency>

        <!-- Thymeleaf -->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf</artifactId>
            <version>${thymeleaf.version}</version>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring3</artifactId>
            <version>${thymeleaf.version}</version>
        </dependency>

        <!-- Spring + Quartz need transaction -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${springframework.version}</version>
        </dependency>

        <!-- QuartzJob -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>${quartz-scheduler.version}</version>
        </dependency>
        
        <!-- Log -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>
    </dependencies>

Sau đó, tiến hành build và update maven sẽ được project ban đầu chúng ta cần.

 

CẤU HÌNH CÁC BEAN CẦN THIẾT CHO SPRING

Trong thư mục src/main/webapp/WEB-INF, tìm đến file web.xml. Đây là file bean cơ sở của project, các bean còn lại chúng ta sẽ khai báo import từ đây.

 

Tạo thư mục mới SPRING (để dễ quản lý chứ không quan trọng) trong WEB-INF, tạo file ApplicationContext.xml, với nội dung như sau. File này sẽ chịu trách nhiệm scan các Spring bean trong toàn project của chúng ta theo main package và import 2 bean email-thymeleafmonitor-spring-quartz-config trong các hướng dẫn tiếp theo.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">

       <context:annotation-config />
       <context:component-scan base-package="com.cominit.demo.sys_monitor.*" />

       <import resource="email-thymeleaf.xml"/>
       <import resource="monitor-spring-quartz-config.xml"/>
</beans>

 

Sau đó, mở file web.xml và cập nhật đoạn code sau. Như vậy thì từ web.xml, spring sẽ đi qua ApplicationContext và đọc bean trong email-thymeleafmonitor-spring-quartz-config.

<web-app>
    <display-name>sys-monitor</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/SPRING/ApplicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

 

CẤU HÌNH QUARTZJOB

Trong thư mục SPRING lúc nãy, tạo file monitor-spring-quartz-config.xml để cấu hình các bean cho quartjob. Nội dung như sau:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <bean id="dailyStatisticReportTask" class="com.cominit.demo.sys_monitor.job.task.DailyStatisticReportTask" />

    <bean name="dailyStatisticReportJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <property name="jobClass" value="com.cominit.demo.sys_monitor.job.DailyStatisticReportJob" />
        <property name="jobDataAsMap">
            <map>
                <entry key="dailyStatisticReportTask" value-ref="dailyStatisticReportTask" />
            </map>
        </property>
        <property name="durability" value="true" />
    </bean>

    <!-- timezone bean: UTC+1 Germany -->
    <bean id="timeZoneUTC1" class="java.util.TimeZone" factory-method="getTimeZone">
       <constructor-arg value="Europe/Berlin"/>
    </bean>

    <!-- Send statistic report every day at 5AM UTC+1 -->
    <bean id="dailyStatisticReportTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="dailyStatisticReportJob" />
        <property name="cronExpression" value="0 0 5 * * ?" />
        <property name="timeZone" ref="timeZoneUTC1" />
    </bean>

    <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
        <property name="jobDetail" ref="dailyStatisticReportJob" />
        <property name="repeatInterval" value="300000" />
        <property name="startDelay" value="1000" />
    </bean>

    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="jobDetails">
            <list>
                <ref bean="dailyStatisticReportJob" />
            </list>
        </property>

        <property name="triggers">
            <list>
<!--                 <ref bean="dailyStatisticReportTrigger" /> -->
                <ref bean="simpleTrigger" />
            </list>
        </property>
    </bean>

</beans>

 

Hãy để ý kỹ trong file này, chúng ta có:

  • Khai báo bean cho dailyStatisticReportTask (sẽ tạo sau). Class này nhiệm vụ là chứa code xử lý chính trong việc collect thông tin và gửi email report.
  • Khai báo bean dailyStatisticReportJob, nó đi kèm với việc khởi tạo bean cho dailyStatisticReportTask.
  • Khởi tạo trigger (1 dạng tương tự alarm hẹn giờ). Có 2 loại trigger cơ bản là:
    • SimpleTrigger: gồm thuộc tính repeatInterval (thời gian lặp lại giữa các job) và startDelay khi job được chạy, đơn vị ms. Trigger này sẽ được gọi ngay khi start project mà không quan tâm đến thời điểm hẹn giờ. Nó giống như là 1 job chạy đều trong 1 khoảng thời gian, chú ý nhầm lẫn với CronTrigger.
    • CronTrigger: bao gồm cronExpression là 1 bộ các quy tắc để đặt lịch (xem chi tiết tại: http://www.quartz-scheduler.org/documentation/quartz-1.x/tutorials/crontrigger). Như  ví dụ trên thì nó chạy hằng ngày vào lúc 5h sáng của múi giờ UTC+1. Múi giờ UTC này được cấu hình trong thuộc tính timeZone (có thể có hoặc ko) gọi tới bean timeZoneUTC1 ngay ở trên. Bean này thực chất là gọi class TimeZone của java và đưa vào value của 1 địa điểm khớp với UTC+1 (danh sách value xem ở đây: http://tutorials.jenkov.com/java-date-time/java-util-timezone.html).
  • Cài đặt gắn kết giữa job và trigger. Vì sao cần? Vì trong 1 file cấu hình quartzjob chúng ta có thể có nhiều bean job khác nhau trong 1 project, và ở đây sẽ có sự bắt cặp theo thứ tự từ trên xuống, theo ví dụ ta có 2 list và dailyStatisticReportJob sẽ tương ứng với trigger simpleTrigger.

Tiếp theo, tiến hành tạo class DailyStatisticReportJob. Lưu ý, chúng ta có khai báo 1 biến là DailyStatisticReportTask và method setDailyStatisticReportTask, phải chú ý tên method là set + tên biến (viết hoa chữ cái đầu) để spring khởi tạo được bean cho DailyStatisticReportTask.

    private DailyStatisticReportTask dailyStatisticReportTask;

    @Override
    protected void executeInternal(JobExecutionContext arg0)
            throws JobExecutionException {
        LOGGER.info("QUARTZ JOB DAILY STATISTIC REPORT STARTED...");
        dailyStatisticReportTask.run();
        LOGGER.info("QUARTZ JOB DAILY STATISTIC REPORT FINISHED...");
    }

    public void setDailyStatisticReportTask(DailyStatisticReportTask dailyStatisticReportTask) {
        this.dailyStatisticReportTask = dailyStatisticReportTask;
    }

 

Tạo tiếp class DailyStatisticReportTask. Trong class ta tiến hành Autowired 1 service (sẽ tạo sau) là EmailService, hỗ trợ gửi email. Các đoạn code tiếp theo để tạo ra đối tượng Email để send bằng emailService.

    @Autowired
    private IEmailService emailService;

    public void run() {
        try {

            Email email = emailService.generateDailyStatisticEmail(new Date().getTime());

            if (emailService.sendEmail(email)) {
                LOGGER.info("DAILY STATISTIC EMAIL SENT");
            } else {
                LOGGER.warn("ERROR OCCURRED WHEN SENDING THE DAILY STATISTIC EMAIL!!!");
            }
        } catch(Exception e) {
            LOGGER.error("DAILY STATISTIC TASK ERROR", e);
        }
    }

 

CÀI ĐẶT EMAIL BÁO CÁO

Nhắc đến đối tượng email, ta tạo them class DTO Email:

public class Email {
    private String[] to;
    private String[] cc;
    private String[] bcc;
    private String subject;
    private String template;
    private Context body;

    public String[] getTo() {
        return to;
    }

    public void setTo(String... to) {
        this.to = to;
    }

    public String[] getCc() {
        return cc;
    }

    public void setCc(String... cc) {
        this.cc = cc;
    }

    public String[] getBcc() {
        return bcc;
    }

    public void setBcc(String... bcc) {
        this.bcc = bcc;
    }

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public String getTemplate() {
        return template + ".html";
    }

    public void setTemplate(String template) {
        this.template = template;
    }

    public Context getBody() {
        return body;
    }

    public void setBody(Context body) {
        this.body = body;
    }

}

 

Và các class hỗ trợ liên quan như Attack, enum AttackStatus. Bài hướng dẫn này sẽ mô phỏng hệ thống gửi các báo cáo các cuộc tấn công vào hệ thống từ bên ngoài hằng ngày về cho admin.

public class Attack {
    private String ip;
    private AttackStatus status;
    
    public Attack(String ip, AttackStatus status) {
        super();
        this.ip = ip;
        this.status = status;
    }
    public String getIp() {
        return ip;
    }
    public void setIp(String ip) {
        this.ip = ip;
    }
    public AttackStatus getStatus() {
        return status;
    }
    public void setStatus(AttackStatus status) {
        this.status = status;
    }

}

 

public enum AttackStatus {
    BLOCKED("BLOCKED"),NOT_BLOCKED("NOT_BLOCKED"); 
    
    AttackStatus(String type) {
        this.type = type;
    }
    
    private String type;
    
    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
    
}

CẤU HÌNH THƯ VIÊN THYMELEAF HỖ TRỢ GỬI EMAIL DẠNG HTML

Ta sẽ sử dụng thư viện này để binding các dữ liệu thô gồm các thông tin và con số vào template được định nghĩa trước dưới dạng html, sau đó gửi đi. Nếu ai quen sử dụng Microsoft Word thì sẽ liên tưởng đến tính năng tương tự là Mail Merge hay còn gọi là trộn thư.

Cũng trong thư mục SPRING, tạo file email-thymeleaf.xml có nội dung sau:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/util
    http://www.springframework.org/schema/util/spring-util.xsd">

    <!-- Email support -->
    <bean id="javaMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
        <property name="session">
            <ref bean="mailSession"></ref>
        </property>
    </bean>

    <bean id="mailSession" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>java:jboss/mail/Default</value>
        </property>
    </bean>

    <!-- THYMELEAF: Template Resolver for email templates -->
    <beans:bean id="emailTemplateResolver"
        class="org.thymeleaf.templateresolver.ClassLoaderTemplateResolver">
        <beans:property name="prefix" value="mailTemplate/" />
        <beans:property name="templateMode" value="HTML5" />
        <beans:property name="characterEncoding" value="UTF-8" />
        <beans:property name="order" value="1" />
        <!-- Template cache is true by default. Set to false if you want -->
        <!-- templates to be automatically updated when modified. -->
        <beans:property name="cacheable" value="true" />
    </beans:bean>


    <!-- THYMELEAF: Template Engine (Spring3-specific version) -->
    <beans:bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine">
        <beans:property name="templateResolvers">
            <beans:set>
                <beans:ref bean="emailTemplateResolver" />
            </beans:set>
        </beans:property>
    </beans:bean>

</beans>

 

Có 2 điểm quan trọng là:

  • Thymeleaf sẽ sử dụng Java Mail để gửi và do đó ta cần dependency java mail trong file pom.xml trước đó.
  • Bắt buộc phải có mailSession là cấu hình tài khoản email gửi đi. Ta sẽ cấu hình trong jBoss và đặt value là java:jboss/mail/Default

 

Trong thư mục src/main/resources, tạo thư mục mailTemplate, và tạo mới 1 template daily-statistic-template.html mẫu như sau:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:remove="all">Template for HTML email</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>

<body>
    <p>Dear Admin,</p>
    <p>
        The Monitoring System has detected <b><span th:text="${count}"></span> attack times</b> (from out-side systems) within last 24hrs in the list below:
    </p>
    <table border="1">
        <tr>
            <th style="width: 400px">IP</th>
            <th style="width: 150px">STATUS</th>
        </tr>
        <tr th:each="attack: ${attacks}">
            <td style="width: 400px"><span th:text="${attack.ip}"></span></td>
            <td style="width: 150px"><span th:text="${attack.status}"></span></td>
        </tr>
    </table>

    <p>
        Regards, <br /><em>Cominit Monitoring System</em>
    </p>
</body>
</html>

 

Chú ý: đến các value ${count}, ${attacks}… ta sẽ binding cho chúng từ EmailService.

 

CẤU HÌNH TÀI KHOẢN GỬI TRONG JBOSS SERVER

Như đã nói, chúng ta sử dụng jBoss server và cấu hình tài khoản mail trong này. Vào thư mục gốc của jBoss trên máy tính, theo địa chỉ \standalone\configuration tìm đến file standalone-full, mở file này lên và edit như sau:

 

Tìm đến đoạn 

<mail-session jndi-name="java:jboss/mail/Default" debug="true">

Đổi thành, thay EMAIL_ADDRESS EMAIL_PASSWORD thành thông số tài khoản mail của bạn: gmail, Hotmail, Yahoo,…

            <mail-session jndi-name="java:jboss/mail/Default" debug="true">
                <smtp-server outbound-socket-binding-ref="mail-smtp">
                    <login name="EMAIL_ADDRESS" password="EMAIL_PASSWORD"/>
                </smtp-server>
            </mail-session>

 

Chuyển đến mục 

<outbound-socket-binding name="mail-smtp">

Đổi thành, với HOST PORT tùy theo từng nhà cung cấp. VD: Gmail host là smtp.gmail.com, port  465

        <outbound-socket-binding name="mail-smtp">
            <remote-destination host="xxx" port="xxx"/>
        </outbound-socket-binding>

 

TẠO EMAILSERVICE VÀ GỬI MAIL 

Bước cuối cùng, tạo EmailService thông qua interface IEmailService, hoặc không cũng đc.

@Service
public class EmailService implements IEmailService {
    private static Logger LOGGER = Logger.getLogger(EmailService.class);

    @Autowired
    private JavaMailSender javaMailSender;

    @Autowired
    private TemplateEngine templateEngine;

    public boolean sendEmail(Email email) {
        try {
            // Prepare message using a Spring helper
            final MimeMessage mimeMessage = this.javaMailSender
                    .createMimeMessage();
            final MimeMessageHelper message = new MimeMessageHelper(
                    mimeMessage, true, "UTF-8");

            message.setFrom("notify-dn-olympus@axonactive.vn");
            message.setSubject(email.getSubject());
            message.setTo(email.getTo());

            // add cc and bcc
            if (email.getCc() != null && !"".equals(email.getCc())) {
                message.setCc(email.getCc());
            }
            if (email.getBcc() != null && !"".equals(email.getBcc())) {
                message.setBcc(email.getBcc());
            }

            // Create the HTML body using Thymeleaf
            final String htmlContent = this.templateEngine.process(
                    email.getTemplate(), email.getBody());
            message.setText(htmlContent, true);

            // Send email
            this.javaMailSender.send(mimeMessage);
            return true;
        } catch (MessagingException | NullPointerException e) {
            LOGGER.error("Cannot send Email ", e);
        }
        return false;
    }

    public Email generateDailyStatisticEmail(long timestamp) {
        // generate data
        List<Attack> attacks = new ArrayList<>();
        attacks.add(new Attack("201.12.111.167", AttackStatus.BLOCKED));
        attacks.add(new Attack("165.1.253.3", AttackStatus.NOT_BLOCKED));
        attacks.add(new Attack("23.243.17.56", AttackStatus.BLOCKED));

        Email email = new Email();

        // generate excel file
        email.setSubject("[Cominit Monitoring System] Daily statistic");
        email.setTo("vodailuong@gmail.com");
        email.setTemplate("daily-statistic-template");

        // content variables
        Context ctx = new Context();
        ctx.setVariable("count", attacks.size());
        ctx.setVariable("attacks", attacks);
        email.setBody(ctx);

        return email;
    }
}

 

Chú ý:

  • Vì đây là Spring Service nên đừng quên annotation @Service trên class.
  • Autowired JavaMailSender và TemplateEngine (của Thymeleaf).
  • Hàm sendMail là cơ bản
  • Hàm generateDailyStatisticEmail để tạo ra đối tượng Email có tích hợp template đã binding các dữ liệu.

 

BUILD VÀ CHẠY THỬ

Sau khi đã hoàn tất tất cả các thành phần cần thiết, ta tiến hành tạo mới 1 server jBoss trên eclipse và add wepapp vừa tạo vào đó. Start để khởi động jBoss và bắt đầu build + deploy ứng dụng của bạn.

 

Kết quả email gửi đến theo thời gian thực


Leave a Comment

Fields with * are required.