大數據技術之_24_電影推薦系統項目_08_項目總結及補充

一 數據加載服務一、目標二、步驟二 離線推薦服務2.1 基於統計性算法一、目標二、步驟2.2 基於隱語義模型(LFM)的協同過濾推薦算法(ALS)一、目標二、步驟2.3 基於 ElasticSearch 的內容推薦算法一、目標二、步驟2.4 基於內容的推薦服務--電影標籤三 實時推薦服務3.1 推薦算法解析3.2 實時推薦算法的實現過程3.3 日誌的預處理四 綜合業務服務4.1 後臺架構4.2 Spring 框架搭建4.3 API 接口規劃五 用戶可視化服務5.1 前端框架搭建5.2 建立與運行項目5.2.1 建立項目骨架5.2.2 添加項目依賴5.2.3 建立模塊、組件與服務5.2.4 調試項目5.2.5 發佈項目六 項目重構6.1 核心模型提取6.2 經過配置的方式來獲取硬編碼的值6.3 項目打包6.3.1 AngularJS 前端文件打包6.3.2 businessServer 下的 java web 項目的打包方式6.3.3 核心模型 項目的打包方式6.3.4 recommender 下的後端文件打包方式6.4 系統部署javascript


一 數據加載服務

一、目標

【MongoDB】
  1)須要將 Movie【電影數據集】數據集加載到 MongoDB 數據庫中的 Movie 表中。
  2)須要將 Rating【用戶對電影的評分數據集】數據集加載到 MongoDB 數據庫中的 Rating 表中。
  3)須要將 Tag【用戶對電影的標籤數據集】數據集加載到 MongoDB 數據庫中的 Tag 表中。css

【ElasticSearch】
  1)須要將 Movie【電影數據集】加載到 ElasticSearch 名叫 Movie 的 Index 中。
  2)須要將 Tag 數據和 Movie 數據作左外鏈接。html

二、步驟

1)先新建一個 Maven 項目,將依賴添加好。
2)分析數據集 Movie、Rating、Tag。
3)新建 case class Movie、Rating、Tag、MongoConfig、ESConfig。
4)加載數據集。
5)將 RDD 數據集裝換成 DataFrame。
6)將 DF 加載到 MongoDB 中:
  1. 將原來的 Collection 所有刪除
  2. 經過 DF 的 write 方法將數據寫入
  3. 建立數據庫索引
  4. 關閉 MongoDB 鏈接
7)將 DF 加載到 ElasticSearch 中:
  1. 將存在的 Index 刪除掉,而後建立新的 Index
  2. 經過 DF 的 write 方法將數據寫入
8)關閉 Spark 集羣前端

二 離線推薦服務

2.1 基於統計性算法

一、目標

一、優質電影
  1)獲取全部歷史數據中評分次數最多的電影的集合,統計每一個電影的評分數 --> RateMoreMoviesjava

二、熱門電影
  1)按照月來統計,這個月中評分次數量最多的電影咱們認爲是熱門電影,統計每月中的每一個電影的評分數量 --> RateMoreRecentlyMoviesjquery

三、統計電影的平均評分
  1)將Rating數據集中全部的用戶評分數據進行平均,計算每個電影的平均評分 --> AverageMoviesScoreangularjs

四、統計每種類別電影的 TOP10 電影
  1)將每種類別的電影中評分最高的 10 個電影分別計算出來 --> GenresTopMovies web

二、步驟

一、新建一個項目 StaticticsRecommender,配置好你的依賴
二、統計全部歷史數據中電影的評分個數
  1)經過 Rating 數據集,用 mid 進行 group by 操做,count 計算總數
三、統計以月爲單位的電影的評分個數
  1)須要註冊一個 UDF 函數,用於將 Timestamp 這種格式的數據轉換成 yyyyMM 這個格式 --> SimpleDateFormat
  2)須要將 RatingDF 轉換成新的 RatingOfMouthDF【只有日期數據發生了轉換】
  3)經過 group by yearmonth, mid 來完成統計
四、統計每一個電影評分得分
  1)經過 Rating 數據集,用戶 mid 進行 group by 操做,avg 計算評分
五、統計每種類別中評分最高的 10 個電影
  1)須要經過 JOIN 操做將電影的平均評分數據和 Movie 數據集進行合併,產生 MovieWithScore 數據集
  2)須要將電影的類別數據轉換成 RDD --> GenresRDD
  3)將 GenresRDD 和 MovieWithScore 數據集進行笛卡爾積,產生一個 N * M 行的數據集
  4)經過過濾操做,過濾掉電影的真實類別和 GenresRDD 中的類別不匹配的電影
  5)經過 Genres 做爲 Key,進行 groupByKey 操做,將相同電影類別的電影進行彙集
  6)經過排序和提取,獲取評分最高的 10 個電影 --> genresTopMoviesDF
  7)將結果輸出到 MongoDB 中redis

統計每種類別中評分最高的 10 個電影圖解:算法

2.2 基於隱語義模型(LFM)的協同過濾推薦算法(ALS)

一、目標

一、訓練 ALS 推薦模型(ALS:交替最小二乘法)
二、計算用戶電影推薦矩陣
三、計算電影類似度矩陣

二、步驟

一、訓練 ALS 推薦模型
  1)須要構建 RDD[Rating] 類型的訓練集數據
  2)直接經過 ALS.train 方法來進行模型訓練

二、計算用戶電影推薦矩陣
  1)生成 userMovies --> RDD[(Int,Int)]
  2)經過 ALS 模型的 predict 方法來預測評分
  3)將數據經過 groupByKey 處理後排序,取前 N 個做爲推薦結果

三、計算電影類似度矩陣
  1)獲取電影的特徵矩陣,轉換成 DoubleMatrix
  2)電影的特徵矩陣之間作笛卡爾積,經過餘弦類似度計算兩個電影的類似度
  3)將數據經過 GroupBy 處理後,輸出

四、ALS 模型的參數選擇
  1)經過計算 ALS 的均方根偏差來判斷參數的優劣程度

2.3 基於 ElasticSearch 的內容推薦算法

一、目標

  基於內容的推薦一般是給定一篇文檔信息,而後給用戶推薦與該文檔相識的文檔。Lucene 的 api 中有實現查詢文章類似度的接口,叫 MoreLikeThis。Elasticsearch 封裝了該接口,經過 Elasticsearch 的 More like this 查詢接口,咱們能夠很是方便的實現基於內容的推薦。
  在本項目中 ElasticSearch 除了提供基礎的模糊檢索功能外,主要提供了電影之間基 於More like this 查詢類似度之間的功能,使電影依據演員、導演、名稱、描述、標籤等進行類似度計算,返回查詢電影的類似電影集合。
  因爲該功能已有 ES 進行實現,故該功能不用提早計算或者實時計算,只是須要在業務服務器查詢推薦集合的時候,將結果集按照業務規則進行合併便可。

二、步驟

核心算法以下:

// 基於內容的推薦算法
private List<Recommendation> findContentBasedMoreLikeThisRecommendations(int mid, int maxItems) {
    MoreLikeThisQueryBuilder query = QueryBuilders.moreLikeThisQuery(
            new String[]{"id"},
            new String[]{"name""descri""genres""actors""directors""tags"},
            new MoreLikeThisQueryBuilder.Item[]{new MoreLikeThisQueryBuilder.Item(Constant.ES_INDEX, Constant.ES_MOVIE_TYPE, String.valueOf(mid))});

    return parseESResponse(esClient.prepareSearch().setQuery(query).setSize(maxItems).execute().actionGet());
}

private List<Recommendation> parseRecs(Document document, int maxItems) {
    List<Recommendation> recommendations = new ArrayList<>();
    if (null == document || document.isEmpty())
        return recommendations;
    ArrayList<Document> recs = document.get("recs", ArrayList.class);
    for (Document recDoc : recs) {
        recommendations.add(new Recommendation(recDoc.getInteger("rid"), recDoc.getDouble("r")));
    }
    Collections.sort(recommendations, new Comparator<Recommendation>() {
        @Override
        public int compare(Recommendation o1, Recommendation o2) {
            return o1.getScore() > o2.getScore() ? -1 : 1;
        }
    });
    return recommendations.subList(0, maxItems > recommendations.size() ? recommendations.size() : maxItems);
}

2.4 基於內容的推薦服務--電影標籤

  原始數據中的 tag 文件,是用戶給電影打上的標籤,這部份內容想要直接轉成評分並不容易,不過咱們能夠將標籤內容進行提取,獲得電影的內容特徵向量,進而能夠經過求取類似度矩陣。這部分能夠與實時推薦系統直接對接,計算出與用戶當前評分電影的類似電影,實現基於內容的實時推薦。爲了不熱門標籤對特徵提取的影響,咱們還能夠經過 TF-IDF 算法(詞頻-逆文檔頻率)對標籤的權重進行調整,從而儘量地接近用戶偏好。

  核心算法以下:

package com.atguigu.content

import org.apache.spark.SparkConf
import org.apache.spark.ml.feature.{HashingTF, IDF, Tokenizer}
import org.apache.spark.ml.linalg.SparseVector
import org.apache.spark.sql.SparkSession
import org.jblas.DoubleMatrix

// 須要的數據源是電影內容信息
case class Movie(mid: Int, name: String, descri: String, timelong: String, issue: String,
                 shoot: String, language: String, genres: String, actors: String, directors: String)


case class MongoConfig(uri: String, db: String)

// 定義一個基準推薦對象
case class Recommendation(mid: Int, score: Double)

// 定義電影內容信息提取出的特徵向量的電影類似度列表
case class MovieRecs(mid: Int, recs: Seq[Recommendation])

object ContentRecommender 
{

  // 定義表名和常量
  val MONGODB_MOVIE_COLLECTION = "Movie"
  val CONTENT_MOVIE_RECS = "ContentMovieRecs"

  def main(args: Array[String]): Unit = {
    val config = Map(
      "spark.cores" -> "local[*]",
      "mongo.uri" -> "mongodb://hadoop102:27017/recommender",
      "mongo.db" -> "recommender"
    )

    // 建立一個 SparkConf 對象
    val sparkConf = new SparkConf().setMaster(config("spark.cores")).setAppName("ContentRecommender")

    // 建立一個 SparkSession 對象
    val spark = SparkSession.builder().config(sparkConf).getOrCreate()

    // 聲明一個隱式的配置對象
    implicit val mongoConfig = MongoConfig(config("mongo.uri"), config("mongo.db"))

    // 在對 DataFrame 和 Dataset 進行許多操做都須要這個包進行支持
    import spark.implicits._

    // 加載數據,並做預處理
    val movieTagsDF = spark.read
      .option("uri", mongoConfig.uri)
      .option("collection", MONGODB_MOVIE_COLLECTION)
      .format("com.mongodb.spark.sql")
      .load()
      .as[Movie]
      .map { // 提取 mid,name,genres 三項做爲原始的內容特徵,分詞器默認分隔符是空格
        x => (x.mid, x.name, x.genres.map(c => if (c == '|'' ' else c))
      }
      .toDF("mid""name""genres")
      .cache()

    // TODO:從內容信息中提取電影特徵的特徵向量
    // 建立一個分詞器,默認按照空格分詞
    val tokenizer = new Tokenizer().setInputCol("genres").setOutputCol("words")
    // 用分詞器對原始數據進行轉換,生成新的一列words
    val wordsData = tokenizer.transform(movieTagsDF)

    // 引入 HashingTF 工具,該工具能夠將詞語序列轉換成對應的詞頻
    val hashingTF = new HashingTF().setInputCol("words").setOutputCol("rawFeatures").setNumFeatures(50)
    val featurizeData = hashingTF.transform(wordsData)

    // 測試
    // wordsData.show()
    // featurizeData.show()
    // featurizeData.show(truncate = false) // 不壓縮顯示

    // 引入 IDF 工具,該工具能夠獲得 IDF 模型
    val idf = new IDF().setInputCol("rawFeatures").setOutputCol("features")
    // 訓練 IDF 模型,獲得每一個詞的逆文檔頻率
    val idfModel = idf.fit(featurizeData)

    // 用 IDF 模型對原數據進行處理,獲得文檔中每一個詞的 TF-IDF,做爲新的特徵向量
    val rescaleData = idfModel.transform(featurizeData)

    // 測試
    // rescaleData.show(truncate = false) // 不壓縮顯示

    val movieFeatures = rescaleData.map(
      row => (row.getAs[Int]("mid"), row.getAs[SparseVector]("features").toArray)
    ).rdd.map(
      x => (x._1, new DoubleMatrix(x._2))
    )

    // 測試
    // movieFeatures.collect().foreach(println)

    // 對全部電影兩兩計算它們的類似度,先作笛卡爾積
    val movieRecs = movieFeatures.cartesian(movieFeatures)
      .filter {
        // 把本身跟本身的配對過濾掉
        case (a, b) => a._1 != b._1
      }
      .map {
        case (a, b) => {
          val simScore = this.consinSim(a._2, b._2)
          (a._1, (b._1, simScore))
        }
      }
      .filter(_._2._2 > 0.6// 過濾出類似度大於 0.6 的
      .groupByKey()
      .map {
        case (mid, recs) => MovieRecs(mid, recs.toList.sortWith(_._2 > _._2).map(x => Recommendation(x._1, x._2)))
      }
      .toDF()

    // 把結果寫入對應的 MongoDB 表中
    movieRecs.write
      .option("uri", mongoConfig.uri)
      .option("collection", CONTENT_MOVIE_RECS)
      .mode("overwrite")
      .format("com.mongodb.spark.sql")
      .save()

    spark.stop()
  }

  // 求兩個向量的餘弦類似度
  def consinSim(movie1: DoubleMatrix, movie2: DoubleMatrix): Double = {
    movie1.dot(movie2) / (movie1.norm2() * movie2.norm2()) // l1範數:向量元素絕對值之和;l2範數:即向量的模長(向量的長度),向量元素的平方和再開方
  }
}

三 實時推薦服務

3.1 推薦算法解析

3.2 實時推薦算法的實現過程

實時推薦算法的前提:
  1.在 Redis 集羣中存儲了每個用戶最近對電影的 K 次評分。實時算法能夠快速獲取。
  2.離線推薦算法已經將電影類似度矩陣提早計算到了 MongoDB 中。
  3.Kafka 已經獲取到了用戶實時的評分數據。
算法過程以下:
  實時推薦算法輸入爲一個評分<userId, mid, rate, timestamp>,而執行的核心內容包括:獲取 uid 最近 K 次評分、獲取 mid 最類似 K 個電影、計算候選電影的推薦優先級、更新對 uid 的實時推薦結果。

3.3 日誌的預處理

一、目標
  1)根據埋點數據信息,格式化日誌
二、步驟
  1)構建 LogProcessor 實現 Processor 接口,實現對於數據的處理
  2)構建 StreamsConfig 配置數據
  3)構建 TopologyBuilder 來設置數據處理拓撲關係
  4)構建 KafkaStreams 來啓動整個處理

四 綜合業務服務

4.1 後臺架構

  


  後臺服務經過 Spring 框架進行建立,主要負責後臺數據和前端業務的交互。項目主要分爲 REST 接口服務層、業務服務層、業務模型以及工具組件層等組成。
  REST 接口服務層:主要經過 Spring MVC 爲 UI 提供了通信接口,主要包括用戶接口、推薦接口、評分接口、查詢接口、標籤接口以及統計接口。
  業務服務層:主要實現了總體系統的業務邏輯,提供了包含電影相對應操做的服務、評分層面的服務、推薦層面的服務、標籤層面的服務以及用戶層面的服務。
  業務模型方面:將推薦、業務請求以及具體業務數據進行模型建立。
  工具組件層面:提供了對 Redis、ES、MongoDB 的客戶端以及項目常量定義。

 

4.2 Spring 框架搭建

  一、添加相對應對的依賴包。
  二、建立 application.xml 配置文件,配置 application context。
  三、建立 application-servlet.xml 配置文件,用於配置 web application context。
  四、配置 web.xml 將 application context 和 web application context 整合。

  在 MovieRecommendSystem 項目下新建一個 Module 項目 businessServer,而後在 MovieRecommendSystem\businessServer\src\main 目錄下新建 webapp 目錄,此時可能會出現一個問題:新建的 webapp 文件夾不能被識別(沒有小藍點),解決問題連接:https://www.cnblogs.com/chenmingjun/p/10920548.html

  注意:若是導入他人已經寫好的項目時,發現導入的項目與本身的整個項「格格不入」時,這時能夠刪除整個項目在 IDEA 中的配置數據,其文件夾是 .idea,而後刪除緩存索引數據並重啓 IEDA(File -> Invalidate Caches / Restart…),再從新加載該項目,從新生成新的 .idea 文件 和 緩存索引數據。便可解決問題!

依賴 pom.xml 文件內容以下:

<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <parent>
        <artifactId>MovieRecommendSystem</artifactId>
        <groupId>com.atguigu</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>businessServer</artifactId>
    <packaging>war</packaging><!-- 說明是一個 web 項目,打的包不是 jar,而是 war 包 -->

    <properties>
        <log4j2.version>2.9.1</log4j2.version>
        <spring.version>4.3.6.RELEASE</spring.version>
        <spring.data.jpa.version>1.11.0.RELEASE</spring.data.jpa.version>
        <jackson.version>2.8.6</jackson.version>
        <servlet.version>3.0.1</servlet.version>
        <es.version>5.6.2</es.version>
        <mongo.version>3.5.0</mongo.version>
        <jedis.version>2.9.0</jedis.version>
    </properties>

    <dependencies>
        <dependency>
            <!-- log4j2 -->
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>${log4j2.version}</version>
        </dependency>

        <dependency>
            <!-- 用於 Servlet 的開發 -->
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${servlet.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>transport</artifactId>
            <version>${es.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mongodb</groupId>
            <artifactId>mongodb-driver</artifactId>
            <version>${mongo.version}</version>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>${jedis.version}</version>
        </dependency>

        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- Spring End -->

        <!-- fasterxml 用於 JSON 和對象之間的轉換 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <!-- fasterxml end -->
    </dependencies>

    <build>
        <finalName>BusinessServer</finalName>
        <plugins>
            <plugin>
                <!-- 該插件用於在 Maven 中提供 Tomcat 運行環境,你還可使用 Jetty 插件 -->
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <!-- 指定運行後能夠訪問的端口 -->
                    <port>8888</port>
                    <!-- 指定了運行時的根目錄 -->
                    <path>/</path>
                    <!-- 當你更改了代碼後,tomcat 自動從新加載 -->
                    <contextReloadable>true</contextReloadable>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

整個目錄結構以下圖所示:

配置文件內容以下:(注意:本博主配置文件的內容是整個項目的)
MovieRecommendSystem\businessServer\src\main\resources\application.xml

<?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: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-4.3.xsd"
>


    <!-- 用於讓 Spring 去掃描整個 com.atguigu.business 目錄下的代碼 -->
    <context:component-scan base-package="com.atguigu.business"/>

    <context:property-placeholder location="classpath:recommend.properties" ignore-unresolvable="true"/>

    <!-- 用於 JSON 和對象之間的轉換,這裏是父容器 -->
    <bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
        <property name="featuresToEnable">
            <array>
                <util:constant static-field="com.fasterxml.jackson.databind.SerializationFeature.CLOSE_CLOSEABLE"/>
            </array>
        </property>
        <property name="featuresToDisable">
            <array>
                <util:constant static-field="com.fasterxml.jackson.databind.SerializationFeature.FAIL_ON_EMPTY_BEANS"/>
            </array>
        </property>
    </bean>
</beans>

MovieRecommendSystem\businessServer\src\main\resources\log4j.properties

log4j.rootLogger=INFO, file, stdout

# write to stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS}  %5p --- [%50t]  %-80c(line:%5L)  :  %m%n

# write to file
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=D:\\learn\\JetBrains\\workspace_idea\\MovieRecommendSystem\\businessServer\\src\\main\\log\\agent.log
log4j.appender.file.MaxFileSize=1024KB
log4j.appender.file.MaxBackupIndex=1
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS}  %5p --- [%50t]  %-80c(line:%6L)  :  %m%n

#log4j.appender.syslog=org.apache.log4j.net.SyslogAppender
#log4j.appender.syslog=com.c4c.dcos.commons.logger.appender.SyslogAppenderExt
#log4j.appender.syslog.SyslogHost= 192.168.25.102
#log4j.appender.syslog.Threshold=INFO
#log4j.appender.syslog.layout=org.apache.log4j.PatternLayout
#log4j.appender.syslog.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS}  %5p --- [%20t]  %-130c:(line:%4L)  :   %m%n
#demo|FATAL|2014-Jul-03 14:34:34,194|main|com.c4c.logdemo.App:(line:15)|send a log

MovieRecommendSystem\businessServer\src\main\resources\log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="error">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

businessServer\src\main\resources\recommend.properties

#elasticSearch
es.host=hadoop102
es.port=9300
es.cluster.name=my-application

#Mongodb
mongo.host=hadoop102
mongo.port=27017

#Redis
redis.host=hadoop102

MovieRecommendSystem\businessServer\src\main\webapp\WEB-INF\application-servlet.xml

<?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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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-4.3.xsd
                           http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd"
>


    <!-- 用於讓 Spring 去掃描整個 com.atguigu.business.rest 目錄下的代碼 -->
    <context:component-scan base-package="com.atguigu.business.rest"/>

    <!-- 可以讓 web 應用啓用 web 方面的註解 -->
    <mvc:annotation-driven>
        <!-- 給 MVC 框架註冊消息裝換器 -->
        <mvc:message-converters register-defaults="true">
            <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
            <!-- 用於 JSON 的轉換 -->
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=UTF-8</value>
                        <value>application/json;charset=UTF-8</value>
                    </list>
                </property>
                <!-- 子容器引用父容器中的 bean 便可,不用子容器本身建立了,父容器 application.xml -->
                <property name="objectMapper" ref="objectMapper" />
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

    <!-- 內容協商解析器 -->
    <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
        <property name="contentNegotiationManager" ref="contentNegotiationManager"/>
        <property name="viewResolvers">
            <list>
                <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                    <property name="prefix" value="/"/>
                    <property name="suffix" value=".jsp"/>
                </bean>
            </list>
        </property>
        <property name="defaultViews">
            <list>
                <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
            </list>
        </property>
        <property name="useNotAcceptableStatusCode" value="true"/>
    </bean>
    <bean id="contentNegotiationManager"
          class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">

        <property name="defaultContentType" value="application/json"/>
        <property name="mediaTypes">
            <value>
                json=application/json
                xml=text/xml
                html=text/html
            </value>
        </property>
    </bean>

    <!-- 用於處理靜態文件用的 -->
    <mvc:default-servlet-handler/>

    <!-- 跨域訪問的支持 -->
    <mvc:cors>
        <mvc:mapping path="/**" allowed-origins="*" allow-credentials="true" max-age="1800" allowed-methods="GET,POST,OPTIONS"/>
    </mvc:cors>
</beans>

MovieRecommendSystem\businessServer\src\main\webapp\WEB-INF\web.xml

<?xml version="1.0" encoding="UTF-8"?>

<web-app version="3.0"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
        http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
>


    <display-name>recommender</display-name>

    <!-- 用於指定 Spring Application Context 的配置文件路徑 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:application.xml</param-value>
    </context-param>
    <listener>
        <!-- Spring 用於監聽 Serlvet 容器的啓動,進而啓動 Spring Application Context -->
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- 配置你的 Servlet -->
    <servlet>
        <servlet-name>recommend</servlet-name>
        <!-- spring 提供的 Servlet 的實現 -->
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <!-- 指定了 Web Application Context 的配置文件 -->
            <param-value>/WEB-INF/application-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>recommend</servlet-name>
        <!-- 要讓 recommend 這個 Servlet 處理全部的 URL -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <!-- 定義你的歡迎文件 -->
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

MovieRecommendSystem\businessServer\src\main\webapp\index.html

<!--這是別人訪問你的網站是看到的主頁面的HTML文件。大多數狀況下你都不用編輯它。
在構建應用時,CLI 會自動把全部 js 和 css 文件添加進去,因此你沒必要在這裏手動添加任何 <script> 或 <link> 標籤。-->

<!DOCTYPE html>
<html>
<head>
    <base href="/">
    <title>MovieLens海外電影推薦系統</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" type="image/x-icon" href="favicon.ico">

</head>

<body>
<movie-app>加載中...</movie-app>
    <script type="text/javascript" src="inline.bundle.js"></script>
    <script type="text/javascript" src="polyfills.bundle.js"></script>
    <script type="text/javascript" src="scripts.bundle.js"></script>
    <script type="text/javascript" src="styles.bundle.js"></script>
    <script type="text/javascript" src="vendor.bundle.js"></script>
    <script type="text/javascript" src="main.bundle.js"></script>
</body>

<script type="application/javascript">
    document.oncontextmenu = function () {
        return false;
    }
    document.onkeydown = function (event{
        var e = event || window.event || arguments.callee.caller.arguments[0];
        if (e && e.keyCode == 116) {
            return false;
        }
    }
</script>
</html>

4.3 API 接口規劃

五 用戶可視化服務

5.1 前端框架搭建

AngularJS 框架:

電影推薦系統前端框架:

5.2 建立與運行項目

詳細文檔參考:https://angular.cn/guide/quickstart

5.2.1 建立項目骨架

在 CMD 中相對應的目錄中執行:ng new my-app,my-app 爲項目的名稱,能夠任意起名稱。

【Src主文件夾】
你的應用代碼位於 src 文件夾中。全部的 Angular 組件、模板、樣式、圖片以及你的應用所需的任何東西都在那裏。
這個文件夾以外的文件都是爲構建應用提供支持用的。

【根目錄文件夾】
src/ 文件夾是項目的根文件夾之一。 其它文件是用來幫助你構建、測試、維護、文檔化和發佈應用的。它們放在根目錄下,和 src/ 平級。

5.2.2 添加項目依賴

在 CMD 中項目目錄中執行:npm install bootstrap --save,添加 bootstrap 依賴。
在 CMD 中項目目錄中執行:npm install jquery --save,添加bootstrap 依賴。
在 CMD 中項目目錄中執行:npm install systemjs --save,添加 bootstrap 依賴。

5.2.3 建立模塊、組件與服務

在 CMD 中項目目錄中執行:ng g module AppRouting,來建立新模塊。
在 CMD 中項目目錄中執行:ng g component home,來建立新組件。
在 CMD 中項目目錄中執行:ng g service service/login,來建立新服務組件。

5.2.4 調試項目

在 CMD 中項目目錄中執行:ng serve –p 3000,啓動整個應用程序。
訪問:http://localhost:4200
當你修改了後臺代碼的時候,瀏覽器自動 Reload。

5.2.5 發佈項目

在 CMD 中項目目錄中執行:ng build,來打包發佈整個應用程序。
會在目錄下生成 dist 文件夾,該文件夾就是最終的發佈程序。

六 項目重構

一、提取公共的模型
  1)將全部模型和共有的常量定義提取到一個 Module 裏面。
  2)將包含模型和常量定義的 Module 引入到相應的模塊裏面。
  3)使用模型 Module 裏面的定義替代模塊中的相應定義。
二、修改程序中的硬編碼
  1)經過配置的方式來獲取硬編碼的值。

6.1 核心模型提取

6.2 經過配置的方式來獲取硬編碼的值

6.3 項目打包

一、針對每個項目分別編輯相對應的打包過程
二、運行 Root 項目下的 package 階段,進行打包

6.3.1 AngularJS 前端文件打包

maven 調用 cmd 命令的的插件

website 的 pom.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <parent>
        <artifactId>MovieRecommendSystem</artifactId>
        <groupId>com.atguigu</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>website</artifactId>

    <build>
        <finalName>website</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <finalName>website</finalName>
                    <descriptors>
                        <descriptors>assembly/dependencies.xml</descriptors>
                    </descriptors>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <executions>
                    <execution>
                        <id>ng-build</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                        <configuration>
                            <executable>ng</executable>
                            <arguments>
                                <argument>build</argument>
                            </arguments>
                            <workingDirectory>${basedir}/website/</workingDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

dependencies.xml

<assembly>
    <id>release</id>
    <formats>
        <format>tar.gz</format>
    </formats>

    <!-- 設置要打包的文件 -->
    <fileSets>
        <fileSet>
            <directory>images</directory>
            <outputDirectory>./images</outputDirectory>
            <includes>
                <include>**</include>
            </includes>
        </fileSet>
    </fileSets>

    <fileSets>
        <fileSet>
            <directory>website/dist</directory>
            <outputDirectory>./</outputDirectory>
            <includes>
                <include>**</include>
            </includes>
        </fileSet>
    </fileSets>
</assembly>

6.3.2 businessServer 下的 java web 項目的打包方式

在要打包的項目中的 pom.xml 文件中只須要添加如下內容:
MovieRecommendSystem\businessServer\pom.xml

    <packaging>war</packaging><!-- 說明是一個 web 項目,打的包不是 jar,而是 war 包 -->

6.3.3 核心模型 項目的打包方式

不用任何設置,默認打的是 jar 包,若是單獨打 recommender 下項目的包,須要先打核心模型包。

6.3.4 recommender 下的後端文件打包方式

在每個要打包的子項目中的 pom.xml 文件中添加如下內容:
例如:MovieRecommendSystem\recommender\DataLoader\pom.xml

    <build>
        <finalName>dataLoader</finalName><!-- 名字任意起 -->
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.atguigu.recommender.DataLoader</mainClass><!-- 修改成對應的主類 -->
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
        </plugins>
    </build>

在 父 的 pom.xml 文件中,對於不須要打進 jar 中的依賴,使用 <scope>provided</scope> 配置便可。以下:
MovieRecommendSystem\recommender\pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <parent>
        <artifactId>MovieRecommendSystem</artifactId>
        <groupId>com.atguigu</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>recommender</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>DataLoader</module>
        <module>StatisticsRecommender</module>
        <module>OfflineRecommender</module>
        <module>StreamingRecommender</module>
        <module>ContentRecommender</module>
        <module>KafkaStreaming</module>
    </modules>

    <!-- 僅申明子項目共有的依賴,並不引入,若是子項目須要此依賴,那麼子項目須要聲明方可引入 -->
    <dependencyManagement>
        <dependencies>
            <!-- 引入 Spark 相關的 Jar 包 -->
            <dependency>
                <groupId>org.apache.spark</groupId>
                <artifactId>spark-core_2.11</artifactId>
                <version>${spark.version}</version>
                <!-- provided 若是存在,那麼運行時該 jar 包不存在,也不會打包到最終的發佈版本中,只是編譯期有效 -->
                <!--<scope>provided</scope>-->
            </dependency>
            <dependency>
                <groupId>org.apache.spark</groupId>
                <artifactId>spark-sql_2.11</artifactId>
                <version>${spark.version}</version>
                <!-- provided 若是存在,那麼運行時該 jar 包不存在,也不會打包到最終的發佈版本中,只是編譯期有效 -->
                <!--<scope>provided</scope>-->
            </dependency>
            <dependency>
                <groupId>org.apache.spark</groupId>
                <artifactId>spark-streaming_2.11</artifactId>
                <version>${spark.version}</version>
                <!-- provided 若是存在,那麼運行時該 jar 包不存在,也不會打包到最終的發佈版本中,只是編譯期有效 -->
                <!--<scope>provided</scope>-->
            </dependency>
            <dependency>
                <groupId>org.apache.spark</groupId>
                <artifactId>spark-mllib_2.11</artifactId>
                <version>${spark.version}</version>
                <!-- provided 若是存在,那麼運行時該 jar 包不存在,也不會打包到最終的發佈版本中,只是編譯期有效 -->
                <!--<scope>provided</scope>-->
            </dependency>
            <dependency>
                <groupId>org.apache.spark</groupId>
                <artifactId>spark-graphx_2.11</artifactId>
                <version>${spark.version}</version>
                <!-- provided 若是存在,那麼運行時該 jar 包不存在,也不會打包到最終的發佈版本中,只是編譯期有效 -->
                <!--<scope>provided</scope>-->
            </dependency>
            <dependency>
                <groupId>org.scala-lang</groupId>
                <artifactId>scala-library</artifactId>
                <version>${scala.version}</version>
                <!-- provided 若是存在,那麼運行時該 jar 包不存在,也不會打包到最終的發佈版本中,只是編譯期有效 -->
                <!--<scope>provided</scope>-->
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <!-- 父項目已聲明該 plugin,子項目在引入的時候,不用聲明版本和已經聲明的配置 -->
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

6.4 系統部署

一、項目的打包

二、前端的部署
  1)將前端的包 website-release.tar.gz 部署在 /var/www/html 中

三、業務服務器的部署
  1)將後臺的代碼解壓複製到 tomcat/webapps/ROOT 下

四、流式計算的部署
  1)啓動 Kafka
  2)編輯 Flume 配置文件
  3)啓動 Flume
  4)啓動 StreamingRecommender 程序

五、離線計算的部署  1)azkaban 啓動  2)打包統計服務和離線推薦服務,並放到 Linux 目錄下  3)編寫 azkaban 的 job 文件,去調度兩個 jar 包

相關文章
相關標籤/搜索