市長信箱郵件查詢服務: 使用SpringBoot構建工程

市長信箱郵件查詢服務: 使用SpringBoot構建工程

一直想用SpringBoot作個微服務,練練手, 爲後續部署到docker打下基礎. 今天比較空閒, 就開始把部分想法落地了.
https://github.com/ybak/mycrawlercss


概覽

用來練手的demo應用是一個市長信箱的內容抓取與檢索頁面. 鑑於個人八卦特質,總想了解下週邊的一些投訴信息. 而成都的市長信箱是一個絕好的信息來源.html

信件格式:

來信狀況 張三
來信標題 生活困擾
來信內容 尊敬市長你好咱們有十三戶污水到我處沒法排走...
辦理結果 郫縣(2016-05-20 11:31:10): 來信人: 您好! ...

這個demo應用的主要功能有:java

  1. 從市長信箱抓取全部的市民投訴並保存jquery

  2. 提供按關鍵字檢索的web頁面來檢索感興趣的投訴信息git

按照按部就班的原則, 先實現只實現基本功能, 不考慮性能, 後續再進行優化.
Mysql的提供了基本的模糊匹配功能, 且SpringBoot中,能方便的集成JPA.
使用Mysql保存抓取信息, 並提供給Web應用查詢, 是很容易實現的. 因此該demo應用的初版技術設計以下:
這裏寫圖片描述
SpringBoot的代碼使用maven的多模塊組織:
父模塊(聲明此工程的spring-boot的版本)
pom.xmlgithub

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.5.RELEASE</version>
</parent>

crawler-downloader:抓取模塊
: pom.xmlweb

crawler-persistence:存儲模塊(使用spring的jpa實現orm)
: pom.xmlspring

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

crawler-search-web:頁面模塊(使用spring的thymeleaf實現mvc和rest api)
: pom.xmlsql

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

接下來,分別介紹各個模塊的細節:docker


crawler-persistence:存儲模塊

根據信件內容的格式,設計存儲信件的表結構以下:
這裏寫圖片描述

抓取郵件信息是的DB操做

這裏我使用的rxjava-jdbc來進行數據的插入.相比JPA, rxjava-jdbc若是作基礎的查詢和插入操做使用起來很方便.

// 查詢郵件詳情url
Iterable<Tuple2<Integer, String>> results = db
.select("select id, url from chengdu12345 limit ?,?").parameters(i * 50, 50)
.getAs(Integer.class, String.class).toBlocking().toIterable();
//插入郵件記錄
int updates = db.update("insert into chengdu12345(url, title, sender, accept_unit, status, category, views, create_date) values (?,?,?,?,?,?,?,?)")
.parameters(url, title, sender, receiveUnit, status, category, views, publishDate)
.execute();

WEB展現的DB操做

查詢數據庫的郵件信息時, 會涉及到分頁, 模糊匹配, 這個時候rxjava-jdbc顯的有些力不從心了. 而spring-data的大量的模板方法,會讓查詢代碼簡化. 因此這裏我使用了spring-data-jpa的方式來進行查詢.

@Table(name = "chengdu12345")
@NamedQuery(name = "Mail.search",
        query = "select m from Mail m where m.title like ?1 or m.content like ?1 or m.result like ?1")
public class Mail implements Serializable {...}
......
public interface MailRepository extends Repository<Mail, Long> {
    Page<Mail> search(String keyword, Pageable pageable);
}
......
public class MailService {
    @Autowired
    private MailRepository mailRepository;

    public Page<Mail> search(String keyword, Pageable pageable) {
        return mailRepository.search("%" + keyword + "%", pageable);
    }
}

MailService 的search方法,只需傳入Pageable 實例, spring-data將自動爲咱們處理好分頁的邏輯, 很是方便.


crawler-downloader:抓取模塊

市長信箱的郵件展現列表中只有郵件標題和郵件詳情連接等基礎信息,沒有郵件正文和處理結果詳情. 個人抓取流程是:

  1. 遍歷全部的郵件列表的分頁信息, 將郵件基礎信息保存到數據庫.

  2. 遍歷數據庫中的全部已保存的郵件基礎信息, 取出郵件詳情連接, 再對該連接進行抓取, 取得內容進行分析並保存到數據庫中.

我用來抓取頁面的http客戶端類庫是okhttp,
okhttp不但提供了簡潔的api, 還在內部創建了url鏈接池, 在快速抓取頁面時, 減小了tcp連接的創建, 提升了速度, 也下降了抓取失敗的概率.

public class HtmlUtil {
    static OkHttpClient client = new OkHttpClient();
    public static String getURLBody(String url) throws IOException {
        Request request = new Request.Builder()
                .url(url)
                .build();
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) {
            response.body().close();
            throw new IllegalArgumentException(response.message());
        }
        return response.body().string();
    }
}

頁面解析的列庫我使用了Jsoup, Jsoup也能夠直接用來抓取頁面. 但它沒有提供易用的鏈接池機制. 默認每次抓取都會建立tcp鏈接. 在快速抓取頁面的狀況下很容易打開過多的端口,從而形成抓取失敗. 但Jsoup的html解析api倒是至關的強大. 尤爲它的對css selector的支持, 選取dom就像使用jquery同樣方便.

String html = HtmlUtil.getURLBody(pageUrl);
Document doc = Jsoup.parse(html);
Elements elements = doc.select("div.left5 ul li.f12px");
for (Element element : elements) {
    String url = urlPrefix + element.select("css").attr("href");
......
}

crawler-search-web:頁面模塊

頁面模塊是使用SpringBoot啓動的模塊. 該模塊功能很是簡單:

  1. 提供一個靜態頁面

  2. 提供一個搜索API

這裏使用Spring MVC來提供實現:

@Controller
public class WelcomeController {
    @Autowired
    private MailService mailService;
    @RequestMapping("/")
    public String welcome() {
        return "welcome";//提供靜態頁面
    }
    @RequestMapping("/search")
    @ResponseBody
    public Page<Mail> search(String keyword) {
        Pageable query = new PageRequest(0, 100);//提供一個搜索API
        return mailService.search(keyword, query);
    }
}

有了Controller和頁面, 剩下的工做就是利用Spring Boot來啓動工程了. 使用Spring Boot啓動應用很是方便, 只需幾行代碼:

@SpringBootApplication(scanBasePackages = {
        "org.ybak.crawler.persistence.service",
        "org.ybak.crawler.web"
})
@EnableJpaRepositories("org.ybak.crawler.persistence.repo")
@EntityScan("org.ybak.crawler.persistence.vo")
public class WebApplication {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(WebApplication.class, args);
    }
}

有了Spring Boot的啓動類, 只用運行main就能夠啓動應用了. 最後的頁面是這樣的:
這裏寫圖片描述


踩過的坑

  1. 應用啓動報錯, 提示"Service not found."
    由於Spring Boot默認會掃描啓動類所在包(org.ybak.crawler.web)下的Spring註解.但個人Service類在另一個包:org.ybak.crawler.persistence.service, 因此Spring啓動時沒有將service初始化. 解決的方法很簡單. 參照上面的WebApplication代碼中scanBasePackages設置, 制定掃描的包列表便可.

  2. 應用啓動報錯, 提示"Repository not found."
    和以前的問題相似, 須要經過EnableJpaRepositories指定repo的掃描路徑.

  3. 應用啓動報錯,提示"Entity not found."
    和以前的問題相似, 須要經過EntityScan指定Entity的掃描路徑.

  4. 使用SpringBoot開發時, 頁面模板文件修改後瀏覽器不生效, Java邏輯修改後不生效.
    引入spring-boot-devtools,該模塊可在調試時設置各類禁止模板緩存的配置, 方便開發調試.

  5. 使用了SpringBootDevtools開發時, 任何文件修改都會致使SpringBoot重啓. 影響開發效率.
    devtools經過重啓來加載新類,讓新代碼生效. 但沒完沒了的重啓也會下降開發效率.幸虧spring提供了spring-loaded工具, 能夠理解爲開源的針對spring的JRebel. 使用了它之後, 就能夠享受無重啓熱部署了.

總結

經過使用Spring Boot來快速實現一個web應用, 確實感覺到它的方便. 大量約定的默認配置能讓代碼簡潔很多, 但當須要自定義配置時, 面對spring-boot凌亂的文檔, 有着實讓人頭大. 必須常常google才能解決不斷冒出的問題.
另外一方面, 使用Spring Boot開發一個簡單的Web應用,並不能展現Spring Boot做爲微服務開發框架的威力. 後續我將調整這個web應用的架構, 配以docker+ elasticsearch, 來實現這個應用的微服務化.
這裏寫圖片描述

相關文章
相關標籤/搜索