Vue開發的電影預告webApp介紹

即將迎來的端午小假期,小夥伴們都準備好怎麼度過了麼😄。我每次出去玩都避免不了去看場電影,此次藉此機會向你們介紹下我開發的能夠查看電影預告片的小項目,但願你們能夠去測試,瀏覽一波即將上映的電影同時能夠幫助我測試一下,指出不足,我都會虛心接受的呦!謝謝你們。javascript

項目演示地址


效果圖





項目介紹

前端是經過vue-cli進行構建項目,後端接口是使用Koa進行編寫的。電影相關數據是使用puppeteer進行爬取並存在mongoDB數據庫中,爲減輕帶寬壓力將預告片上傳到七牛雲上。其主要功能包括:html

  • 電影列表的展現
  • 電影詳情信息及預告片播放功能
  • 根據上映狀況、分類、評分進行篩選電影
  • 電影熱度前十榜單
  • 搜索電影功能
  • 用戶的註冊與登陸。

將來想完善的功能:前端

  • 對電影的收藏與喜歡
  • 根據所在地推薦購票地點
  • 用戶信息相關的操做
  • 電影數據的自動爬取更新
  • 項目web端、小程序端

技術問題

電影上映狀態路由切換問題

電影上映狀態分爲正在熱映與即將上映,其中list路由頁是經過參數進行轉換,1爲正在上映,2爲即將上映。路由配置以下:vue

{  
  path: '/movie',  
  name: 'movie',  
  component: Movie, 
  children: [
    {      
      path: 'all/:type',    
      name: 'list',  
      component: List    
    }  
  ]
}複製代碼

同路由組件參數切換不會再次觸發createdmounted生命週期函數,因此要實現參數切換從新請求數據須要在組件內導航守衛beforeRouteUpdate進行操做。其核心代碼以下:java

beforeRouteUpdate (to, from, next) {
  this.page = 1   
  this.max_page = 0  
  this.movies = []  
  this._getMovies(to.params.type)  
  next()
}複製代碼

應對不一樣場合的Card組件

本項目頁面中大量用本身寫的Card組件,在list頁面、搜索頁面、篩選頁面、榜單頁面等均有使用到。其主要效果以下圖:git



但當在榜單頁面時全部Card組件前都須要有排名,因此能夠經過擴展組件的props實現,新增一個rank屬性,當爲true時則將排名展現出來,其代碼以下:github

<p class="text" v-if="rank" :class="'rank-' + index">{{index}}</p>複製代碼

props: {  
  movie: Object,  
  index: Number,  
  rank: {    
    type: Boolean,   
    default: false  
  }
}複製代碼

電影數據爬取

電影相關數據信息是使用doubanApi結合puppeteer進行爬取獲得的,獲取電影數據總共分爲四步:web

  1. 利用puppeteer模擬瀏覽器訪問豆瓣網站獲取電影的名字、海報、doubanId、評分存入數據庫。爬取網址是:

    const nowUrl = 'https://movie.douban.com/cinema/nowplaying/beijing/'
    const comUrl = 'https://movie.douban.com/coming'複製代碼

  2. 利用豆瓣提供的開放API,經過循環數據庫中電影doubanId來獲取到電影詳細的信息,例如導演、演員、簡介、類型、上映日期等。
  3. 利用puppeteer瀏覽豆瓣電影詳情頁,從而跳轉到預告片頁面爬取預告片的資源,存入數據庫。爬取網址是:

    const url = 'https://movie.douban.com/subject/'
    複製代碼

  4. 使用七牛雲提供的NodeSDK將視頻資源上傳到七牛雲牀上,並將返回的key值存在數據庫中,經過服務器CNAME能夠訪問七牛雲上的短片。其核心代碼以下:

    // 上傳函數
    const uploadToQiniu = async (url, key) => {  
      return new Promise((resolve, reject) => {   
        bucketManager.fetch(url, bucket, key, function (err, respBody, respInfo) {   
          if (err) {
            reject(err)      
          } else {
            if (respInfo.statusCode == 200) {  
              resolve({key})       
            } else { 
              reject(respBody)    
            }     
          }  
        }) 
      })
    }
    // 循環數據庫中數據將上傳後返回的keuy值存在數據庫
    ;(async () => {
      const movies = await Movie.find({
        $or: [
          {videoKey: {$exists: false}},
          {videoKey: null},
          {videoKey: ''}
        ]
      })
      for (let i = 0; i < movies.length; i++) {
        let movie = movies[i]
        if (movie.video && !movie.videoKey) {
          try {
             let videoData = await uploadToQiniu(movie.video, nanoid() + '.mp4')
            let posterData = await uploadToQiniu(movie.poster, nanoid() + '.jpg')
            let coverData = await uploadToQiniu(movie.cover, nanoid() + '.jpg')
            const arr = []
            for (let i = 0; i < movie.images.length; i++) {
              let { key } = await uploadToQiniu(movie.images[i], nanoid() + '.jpg')
              if (key) {
                arr.push(key)
              }
            }
            movie.images = arr
            for (let j = 0; j < movie.casts.length; j++) {
              if (!movie.casts[j].avatar) continue;
              let { key } = await uploadToQiniu(movie.casts[j].avatar, nanoid() + '.jpg')
              if (key) {
                movie.casts[j].avatar = key
              }
            }
            if (videoData.key) {
              movie.videoKey = videoData.key
            }
            if (posterData.key) {
              movie.posterKey = posterData.key
            }
            if (coverData.key) {
              movie.coverKey = coverData.key
            }
            await movie.save()
          } catch (error) {
            console.log(error)
          }
        }
      }
    })()複製代碼

利用Decorator修飾器定義Route路由類

本項目是經過koa-router進行攔截請求,並進行數據庫相關操做,因爲接口數量較多,因此能夠採用Decorator方式去定義路由,更利於開發與維護。例如:vue-cli

// 利用Decorator修飾類的行爲
@controller('api/client/movie')export class movieController {
  @get('/get_all') // 獲取符合條件的電影條數
  @required({
    query: ['page_size', 'page']
  })
  async getAll (ctx, next) {
    const { page_size, page, type } = ctx.query
    const data = await getAllMovies(page_size, page, type)
    ctx.body = {
      code: 0,
      errmsg: '',
      data
    }
  }
  ......
}複製代碼

若是想讓上述代碼有效,須要在項目運行時將修飾器函數定義好,而且載入koa-router中間件,符合修飾器參數的路由則執行相關類實例的方法,其Route類實現代碼以下:數據庫

export class Route {  
  constructor (app, apiPath) {
  this.app = app    
  this.apiPath = apiPath    
  this.router = new Router()  
  }  
  /** * 遍歷routerMap,獲得請求路徑和方法,路徑和controller裝飾器的參數拼接 * 經過koa-router實例調用請求方法(請求路徑, 對應的路由中間件) * 經過koa實例載入router中間件 */  
  init () {    
    glob.sync(path.resolve(__dirname, this.apiPath, './**/*.js')).forEach(require)    
    for (let [conf, controllers] of routerMap) {      
      controllers = toArray(controllers)    
      const prefixPath = conf.target[symbolPrefix]     
      prefixPath && (prefixPath = normalizePath(prefixPath))  
      const routerPath = prefixPath + conf.path      
      this.router[conf.method](routerPath, ...controllers)   
    }    
    this.app.use(this.router.routes()).use(this.router.allowedMethods())  
  }
}
// 將path統一成 '/xxx'
const normalizePath = path => path.startsWith('/')? path : `/${path}`
// 將路由類,請求路徑以及方法,裝飾器對應的方法存入routerMap中
export const router = conf => (target, key, desc) => {
  conf.path = normalizePath(conf.path)  
  routerMap.set({  
    target,    
    ...conf 
  }, target[key])
}
// 將path掛載到路由類的prototyp上,實例上能夠訪問 
export const controller = path => target => (target.prototype[symbolPrefix] = path)
export const get = path => router({  path,  method: 'get'})複製代碼

總結

項目整體來講較爲簡單,並且有不少不足的地方,以後我也會一直完善項目,但願小夥伴們能夠提出不足,以及本身的建議。還有這是我第一次寫文章,水平有限,寫不出深層次的知識,只好拿本身項目做爲處女做😂。但願各位小夥伴多多包涵。最後,若是感受項目還不錯的,不要吝嗇你的star呦!謝謝!

GitHub項目地址

相關文章
相關標籤/搜索