Vue開發的電影預告webApp介紹

lihaozecq發表於2018-06-15

即將迎來的端午小假期,小夥伴們都準備好怎麼度過了麼?。我每次出去玩都避免不了去看場電影,這次藉此機會向大家介紹下我開發的可以檢視電影預告片的小專案,希望大家可以去測試,瀏覽一波即將上映的電影同時可以幫助我測試一下,指出不足,我都會虛心接受的呦!謝謝大家。

專案演示地址

Vue開發的電影預告webApp介紹

效果圖

Vue開發的電影預告webApp介紹


Vue開發的電影預告webApp介紹

Vue開發的電影預告webApp介紹

Vue開發的電影預告webApp介紹

Vue開發的電影預告webApp介紹

專案介紹

前端是通過vue-cli進行構建專案,後端介面是使用Koa進行編寫的。電影相關資料是使用puppeteer進行爬取並存在mongoDB資料庫中,為減輕頻寬壓力將預告片上傳到七牛雲上。其主要功能包括:

  • 電影列表的展示
  • 電影詳情資訊及預告片播放功能
  • 根據上映情況、分類、評分進行篩選電影
  • 電影熱度前十榜單
  • 搜尋電影功能
  • 使用者的註冊與登入。

未來想完善的功能:

  • 對電影的收藏與喜歡
  • 根據所在地推薦購票地點
  • 使用者資訊相關的操作
  • 電影資料的自動爬取更新
  • 專案web端、小程式端

技術問題

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

電影上映狀態分為正在熱映與即將上映,其中list路由頁是通過引數進行轉換,1為正在上映,2為即將上映。路由配置如下:

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

同路由元件引數切換不會再次觸發createdmounted生命週期函式,所以要實現引數切換重新請求資料需要在元件內導航守衛beforeRouteUpdate進行操作。其核心程式碼如下:

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

應對不同場合的Card元件

本專案頁面中大量用自己寫的Card元件,在list頁面、搜尋頁面、篩選頁面、榜單頁面等均有使用到。其主要效果如下圖:

Vue開發的電影預告webApp介紹

Vue開發的電影預告webApp介紹

但當在榜單頁面時所有Card元件前都需要有排名,所以可以通過擴充套件元件的props實現,新增一個rank屬性,當為true時則將排名展示出來,其程式碼如下:

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

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

電影資料爬取

電影相關資料資訊是使用doubanApi結合puppeteer進行爬取得到的,獲取電影資料總共分為四步:

  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方式去定義路由,更利於開發與維護。例如:

// 利用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專案地址

Vue開發的電影預告webApp介紹


相關文章