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


一 數據加載服務1、目標2、步驟二 離線推薦服務2.1 基於統計性算法1、目標2、步驟2.2 基於隱語義模型(LFM)的協同過濾推薦算法(ALS)1、目標2、步驟2.3 基於 ElasticSearch 的內容推薦算法1、目標2、步驟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 系統部署


一 數據加載服務

1、目標

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

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

2、步驟

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、目標

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

2、熱門電影
  1)按照月來統計,這個月中評分次數量最多的電影我們認為是熱門電影,統計每個月中的每個電影的評分數量 --> RateMoreRecentlyMovies

3、統計電影的平均評分
  1)將Rating數據集中所有的用戶評分數據進行平均,計算每一個電影的平均評分 --> AverageMoviesScore

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

2、步驟

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

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

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

1、目標

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

2、步驟

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

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

3、計算電影相似度矩陣
  1)獲取電影的特征矩陣,轉換成 DoubleMatrix
  2)電影的特征矩陣之間做笛卡爾積,通過余弦相似度計算兩個電影的相似度
  3)將數據通過 GroupBy 處理后,輸出

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

2.3 基於 ElasticSearch 的內容推薦算法

1、目標

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

2、步驟

核心算法如下:

// 基於內容的推薦算法
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)根據埋點數據信息,格式化日志
2、步驟
  1)構建 LogProcessor 實現 Processor 接口,實現對於數據的處理
  2)構建 StreamsConfig 配置數據
  3)構建 TopologyBuilder 來設置數據處理拓撲關系
  4)構建 KafkaStreams 來啟動整個處理

四 綜合業務服務

4.1 后台架構

  


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

 

4.2 Spring 框架搭建

  1、添加相對應對的依賴包。
  2、創建 application.xml 配置文件,配置 application context。
  3、創建 application-servlet.xml 配置文件,用於配置 web application context。
  4、配置 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、提取公共的模型
  1)將所有模型和共有的常量定義提取到一個 Module 里面。
  2)將包含模型和常量定義的 Module 引入到相應的模塊里面。
  3)使用模型 Module 里面的定義替代模塊中的相應定義。
2、修改程序中的硬編碼
  1)通過配置的方式來獲取硬編碼的值。

6.1 核心模型提取

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

6.3 項目打包

1、針對每一個項目分別編輯相對應的打包過程
2、運行 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、項目的打包

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

3、業務服務器的部署
  1)將后台的代碼解壓復制到 tomcat/webapps/ROOT 下

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

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


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com