commit
cb4f28b1bf
@ -0,0 +1,6 @@
|
||||
/.classpath
|
||||
/.project
|
||||
target
|
||||
conf
|
||||
logs
|
||||
.settings
|
||||
@ -0,0 +1,119 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>guerrilla</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>guerrilla</name>
|
||||
<description>Hit and Run</description>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<spring-boot-dependencies.version>3.4.4</spring-boot-dependencies.version>
|
||||
<maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
|
||||
<jsoup.version>1.19.1</jsoup.version>
|
||||
<java.mail.version>1.6.2</java.mail.version>
|
||||
<guava.version>33.1.0-jre</guava.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring-boot-dependencies.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>${guava.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.htmlunit</groupId>
|
||||
<artifactId>htmlunit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>${jsoup.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.mail</groupId>
|
||||
<artifactId>javax.mail-api</artifactId>
|
||||
<version>${java.mail.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sun.mail</groupId>
|
||||
<artifactId>javax.mail</artifactId>
|
||||
<version>${java.mail.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jdk8</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven-compiler-plugin.version}</version>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring-boot-dependencies.version}</version>
|
||||
<configuration>
|
||||
<mainClass>com.example.guerrilla.Boot</mainClass>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>build-info</id>
|
||||
<goals>
|
||||
<goal>build-info</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@ -0,0 +1,40 @@
|
||||
package com.example.guerrilla;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
|
||||
import com.example.guerrilla.service.CaptureService;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Boot {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(Boot.class);
|
||||
|
||||
@Autowired
|
||||
CaptureService captureService;
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
String configDirectory = "conf";
|
||||
if (args.length > 0) {
|
||||
configDirectory = args[0];
|
||||
}
|
||||
logger.info("config directory: {}", configDirectory);
|
||||
|
||||
if (new File(configDirectory).exists() && new File(configDirectory).isDirectory()) {
|
||||
System.setProperty("spring.config.location", configDirectory + "/springboot.yml");
|
||||
System.setProperty("logging.config", configDirectory + "/logback.xml");
|
||||
}
|
||||
CaptureService captureService = new SpringApplicationBuilder(Boot.class).web(WebApplicationType.NONE).run(args)
|
||||
.getBean(CaptureService.class);
|
||||
captureService.run();
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
package com.example.guerrilla.config;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
|
||||
@Value("${app.main.javamail-config}")
|
||||
private String javamailCfg;
|
||||
|
||||
@Bean
|
||||
public Properties javaMailProperties() throws IOException {
|
||||
Properties javaMailProperties = new Properties();
|
||||
FileInputStream fis = new FileInputStream(javamailCfg);
|
||||
javaMailProperties.load(fis);
|
||||
fis.close();
|
||||
return javaMailProperties;
|
||||
}
|
||||
|
||||
@Bean("objectMapper")
|
||||
public ObjectMapper objectMapper() {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
mapper.registerModule(new Jdk8Module());
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "app.htmlunit")
|
||||
public class WebPageList {
|
||||
private List<WebPage> vipPages = new ArrayList<>();
|
||||
|
||||
public List<WebPage> getVipPages() {
|
||||
return vipPages;
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "vip-pages")
|
||||
public void setVipPages(List<WebPage> vipPages) {
|
||||
this.vipPages = vipPages;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return ReflectionToStringBuilder.toString(this);
|
||||
}
|
||||
}
|
||||
|
||||
public record WebPage(String name, String url) {
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,193 @@
|
||||
package com.example.guerrilla.service;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.htmlunit.WebClient;
|
||||
import org.htmlunit.html.HtmlButton;
|
||||
import org.htmlunit.html.HtmlForm;
|
||||
import org.htmlunit.html.HtmlPage;
|
||||
import org.htmlunit.html.HtmlPasswordInput;
|
||||
import org.htmlunit.html.HtmlTextInput;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.example.guerrilla.config.AppConfig.WebPage;
|
||||
import com.example.guerrilla.config.AppConfig.WebPageList;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.collect.MapDifference;
|
||||
import com.google.common.collect.MapDifference.ValueDifference;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
@Service
|
||||
public class CaptureService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CaptureService.class);
|
||||
|
||||
@Value("${app.htmlunit.login-url}")
|
||||
private String loginUrl;
|
||||
|
||||
@Value("${app.htmlunit.login-btn-selector}")
|
||||
private String loginBtnSelector;
|
||||
|
||||
@Value("${app.htmlunit.login-wait-millis:3000}")
|
||||
private Long loginWaitMillis;
|
||||
|
||||
@Value("${app.htmlunit.logout-url}")
|
||||
private String logoutUrl;
|
||||
|
||||
@Value("${app.htmlunit.selector1}")
|
||||
private String selector1;
|
||||
|
||||
@Value("${app.htmlunit.selector2}")
|
||||
private String selector2;
|
||||
|
||||
@Value("${app.htmlunit.selector3}")
|
||||
private String selector3;
|
||||
|
||||
@Value("${app.htmlunit.username}")
|
||||
private String username;
|
||||
|
||||
@Value("${app.htmlunit.password}")
|
||||
private String password;
|
||||
|
||||
@Value("${app.htmlunit.user-page-url}")
|
||||
private String userPageUrl;
|
||||
|
||||
@Autowired
|
||||
private WebPageList webPageList;
|
||||
|
||||
@Value("${app.main.expected-result-file-path}")
|
||||
private String expectedResultFilePath;
|
||||
|
||||
@Value("${app.main.email-password:}")
|
||||
private String smtpPassword;
|
||||
|
||||
@Value("${app.main.email-sender:}")
|
||||
private String fromAddress;
|
||||
|
||||
@Value("${app.main.email-recipient:}")
|
||||
private String[] toAddresses;
|
||||
|
||||
@Value("${app.main.alert-subject}")
|
||||
private String alertSubject;
|
||||
|
||||
@Autowired
|
||||
private EmailService emailService;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
public HashMap<String, Object> convertStringToMap(String jsonString) throws IOException {
|
||||
TypeReference<java.util.HashMap<String, Object>> ref = new TypeReference<>() {
|
||||
};
|
||||
return new HashMap<>(objectMapper.readValue(jsonString, ref));
|
||||
}
|
||||
|
||||
public void run() throws IOException {
|
||||
logger.info("webPageList: {}", webPageList);
|
||||
File jsonFile = new File(expectedResultFilePath);
|
||||
if (!jsonFile.exists()) {
|
||||
logger.info("file does not exist: {}", expectedResultFilePath);
|
||||
return;
|
||||
}
|
||||
String jsonString = FileUtils.readFileToString(jsonFile, StandardCharsets.UTF_8);
|
||||
HashMap<String, Object> expectedMap = convertStringToMap(jsonString);
|
||||
logger.info("expected result: {}", expectedMap);
|
||||
|
||||
Map<String, Object> actualMap = getData();
|
||||
logger.info("actual result: {}", actualMap);
|
||||
MapDifference<String, Object> diff = Maps.difference(expectedMap, actualMap);
|
||||
logger.info("diff.areEqual: {}", diff.areEqual());
|
||||
|
||||
if (!diff.areEqual()) {
|
||||
StringBuilder contentBuilder = new StringBuilder();
|
||||
Map<String, ValueDifference<Object>> entriesDiffering = diff.entriesDiffering();
|
||||
logger.info("entriesDiffering.size(): {}", entriesDiffering.size());
|
||||
entriesDiffering.entrySet().stream().forEach(entry -> {
|
||||
logger.info("entriesDiffering key: {}", entry.getKey());
|
||||
logger.info("entriesDiffering expected: {}", entry.getValue().leftValue());
|
||||
logger.info("entriesDiffering actual: {}", entry.getValue().rightValue());
|
||||
contentBuilder.append(entry.getKey() + "<br/>\n");
|
||||
contentBuilder.append("EXPECTED: " + entry.getValue().leftValue() + "<br/>\n");
|
||||
contentBuilder.append("ACTUAL: " + entry.getValue().rightValue() + "<br/>\n");
|
||||
contentBuilder.append("<br/>\n");
|
||||
});
|
||||
Pair<Boolean, Boolean> sendEmailResult = Pair.of(Boolean.TRUE, emailService.sendEmail(fromAddress,
|
||||
smtpPassword, toAddresses, alertSubject, contentBuilder.toString()));
|
||||
logger.info("sendEmailResult: {}", sendEmailResult);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getData() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try (final WebClient webClient = new WebClient()) {
|
||||
|
||||
final HtmlPage loginPage = webClient.getPage(loginUrl);
|
||||
logger.info("loginPage: {}", loginPage.asXml());
|
||||
|
||||
final HtmlForm form = loginPage.getForms().get(0);
|
||||
final HtmlTextInput usernameField = form.getInputByName("username");
|
||||
usernameField.type(username);
|
||||
logger.info("entered username: {}", username);
|
||||
|
||||
final HtmlPasswordInput passwordField = form.getInputByName("password");
|
||||
passwordField.type(password);
|
||||
logger.info("entered password: {}", password);
|
||||
|
||||
HtmlButton loginBtn = loginPage.querySelector(loginBtnSelector);
|
||||
logger.info("loginBtn type: {}", loginBtn.getType());
|
||||
loginBtn.click();
|
||||
|
||||
Thread.sleep(loginWaitMillis);
|
||||
|
||||
final HtmlPage userPage = webClient.getPage(userPageUrl);
|
||||
logger.info("userPage: {}", userPage.asXml());
|
||||
|
||||
webPageList.getVipPages().forEach(webPage -> {
|
||||
try {
|
||||
fillResponseMap(webClient, webPage, result);
|
||||
} catch (Exception e) {
|
||||
logger.error("getData error!", e);
|
||||
}
|
||||
});
|
||||
webClient.getPage(logoutUrl);
|
||||
} catch (Exception e) {
|
||||
logger.error("getData error!", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void fillResponseMap(WebClient webClient, WebPage webPage, Map<String, Object> result) throws Exception {
|
||||
Map<String, List<String>> reply = new HashMap<>();
|
||||
HtmlPage vipPage = webClient.getPage(webPage.url());
|
||||
logger.info("{}: {}", webPage.name(), vipPage.asXml());
|
||||
Document doc = Jsoup.parse(vipPage.asXml());
|
||||
for (String selector : List.of(selector1, selector2, selector3)) {
|
||||
Elements elements = doc.select(selector);
|
||||
List<String> selectedList = new ArrayList<>();
|
||||
for (Element element : elements) {
|
||||
logger.info("[fillResponseMap] {} -> {}", selector, element.html());
|
||||
selectedList.add(element.html());
|
||||
}
|
||||
reply.put(selector, selectedList);
|
||||
}
|
||||
result.put(webPage.name(), reply);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
package com.example.guerrilla.service;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.mail.Message;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.Transport;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class EmailService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(EmailService.class);
|
||||
|
||||
@Autowired
|
||||
private Properties javaMailProperties;
|
||||
|
||||
public boolean sendEmail(String fromAddress, String smtpPassword, String[] toAddresses, String subject,
|
||||
String content) {
|
||||
try {
|
||||
logger.debug("[sendEmail] fromAddress: {}, toAddresses: {}", fromAddress, toAddresses);
|
||||
if (StringUtils.isEmpty(subject) || StringUtils.isEmpty(smtpPassword) || StringUtils.isEmpty(fromAddress)
|
||||
|| toAddresses.length == 0) {
|
||||
logger.warn("[sendEmail] subject, smtp-password, source-address or target-address is not set");
|
||||
return false;
|
||||
}
|
||||
String emailHost = javaMailProperties.getProperty("mail.smtp.host");
|
||||
logger.debug("[sendEmail] {} -> {}, mail.smtp.host: {}", fromAddress, List.of(toAddresses), emailHost);
|
||||
if (StringUtils.isNotBlank(emailHost) && StringUtils.isNotBlank(fromAddress) && toAddresses.length > 0) {
|
||||
logger.debug("[sendEmail] starting to send message from: {}", fromAddress);
|
||||
Session session = Session.getInstance(javaMailProperties);
|
||||
|
||||
MimeMessage message = new MimeMessage(session);
|
||||
message.setContent(content, "text/html; charset=utf-8");
|
||||
message.setFrom(new InternetAddress(fromAddress));
|
||||
|
||||
Arrays.asList(toAddresses).forEach(toAddress -> {
|
||||
try {
|
||||
message.addRecipient(Message.RecipientType.TO, new InternetAddress(toAddress));
|
||||
} catch (Exception e) {
|
||||
logger.error("sendEmail error!", e);
|
||||
}
|
||||
});
|
||||
message.setSubject(subject);
|
||||
|
||||
Transport transport = session.getTransport("smtp");
|
||||
transport.connect(emailHost, fromAddress, smtpPassword);
|
||||
transport.sendMessage(message, message.getAllRecipients());
|
||||
transport.close();
|
||||
logger.debug("sendEmail to: {}", Arrays.asList(toAddresses));
|
||||
|
||||
return true;
|
||||
} else {
|
||||
logger.warn("[sendEmail] missing either from-field, to-field or host address!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("sendEmail error!", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue