The upcoming Dragon Boat Festival holiday, small partners are ready to spend it 😄. Every time I go out to play, I can’t avoid going to see a movie. This time, I would like to take this opportunity to introduce to you a small project that I developed to check the movie trailers. I hope you can go to test, browse a wave of upcoming movies and help me test and point out the shortcomings. Thank you very much.

Project Demo Address



rendering








Project introduction

The front end is built with vue-CLI, and the back end interface is written using Koa. Puppeteer was used to crawl the data related to the movie and stored in the mongoDB database. In order to reduce the bandwidth pressure, the trailer was uploaded to Qiniuyun. Its main functions include:

  • A display of movie listings
  • Movie details and trailer playback function
  • Select films based on their release, classification, and ratings
  • Top ten hottest movies
  • Movie Search
  • User registration and login.

Features to improve in the future:

  • Collection and love of movies
  • Recommend the place to buy tickets according to the location
  • Operations related to user information
  • Automatic crawling update of movie data
  • Project Web side, small program side

Technical problems

The routing switchover of the movie screening status is faulty

The release status of a movie is divided into hot and upcoming release. The list routing page is converted by parameters. 1 indicates that the movie is being released, and 2 indicates that the movie will be released soon. Route configuration is as follows:

{  
  path: '/movie'.name: 'movie'.component: Movie, 
  children: [{path: 'all/:type'.name: 'list'.component: List    
    }  
  ]
}Copy the code

Parameter switching on the same routing component does not trigger the created or Mounted life cycle functions again. Therefore, to implement parameter switching and request data again, beforeRouteUpdate in the navigation guard in the component must be operated. The core code is as follows:

beforeRouteUpdate (to, from, next) {
  this.page = 1   
  this.max_page = 0  
  this.movies = []  
  this._getMovies(to.params.type)  
  next()
}Copy the code

Card components for different occasions

A large number of self-written Card components are used in the page of this project, including list page, search page, filter page, list page, etc. The main effects are as follows:





But when on the list page, all Card components need to have a rank in front of them, so you can extend the component props to add a rank property that will display the rank when true. The code looks like this:

<p class="text" v-if="rank" :class="'rank-' + index">{{index}}</p>Copy the code

props: {  
  movie: Object.index: Number.rank: {    
    type: Boolean.default: false}}Copy the code

Movie data crawl

The data and information related to the film are obtained by crawling with doubanApi and puppeteer. There are four steps to obtain the film data:

  1. usingpuppeteerThe simulation browser visits the Website of Douban to get the movie’s name, poster, doubanId and score into the database. The crawl site is:

    const nowUrl = 'https://movie.douban.com/cinema/nowplaying/beijing/'
    const comUrl = 'https://movie.douban.com/coming'Copy the code

  2. The open API provided by Douban is utilized to obtain the detailed information of the film, such as director, actor, brief introduction, type, release date and so on, by recycling the film doubanId in the database.
  3. usingpuppeteerBrowse the movie details page of Douban, jump to the trailer page, climb the trailer resources, save to the database. The crawl site is:

    const url = 'https://movie.douban.com/subject/'
    Copy the code

  4. Use the NodeSDK provided by Qiniuyun to upload the video resources to the bed of Qiniuyun, and save the returned key value in the database. The short film on Qiniuyun can be accessed through the server CNAME. The core code is as follows:

    // Upload the function
    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)    
            }     
          }  
        }) 
      })
    }
    // The keuy value returned after the upload is stored in the database
    ;(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)
          }
        }
      }
    })()Copy the code

Define the Route class using the Decorator

This project intercepts requests through KOA-Router and performs database-related operations. Due to the large number of interfaces, Decorator can be used to define routes, which is more convenient for development and maintenance. Such as:

// Use decorators to modify the behavior of a class
@controller('api/client/movie')export class movieController {
  @get('/get_all') // Obtain the number of eligible movies
  @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
    }
  }
  ......
}Copy the code

For the above code to be valid, the modifier function needs to be defined at project runtime and loaded into the KOA-Router middleware. Routes conforming to the modifier parameters will execute the method of the relevant class instance. The Route class implementation code is as follows:

export class Route {  
  constructor (app, apiPath) {
  this.app = app    
  this.apiPath = apiPath    
  this.router = new Router()  
  }  
  /** * Iterates through the routerMap to get the request path and method, the path and controller decorator parameters are concatenated * invoke the request method (request path, corresponding routing middleware) via the KOA-Router instance */  
  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())  
  }
}
// Set path to '/ XXX '
const normalizePath = path= > path.startsWith('/')? path : ` /${path}`
// Store the route class, request path, and decorator methods in the routerMap
export const router = conf= >(target, key, desc) => { conf.path = normalizePath(conf.path) routerMap.set({ target, ... conf }, target[key]) }// Attach the path to the prototyp of the routing class, and the instance can access it
export const controller = path= > target => (target.prototype[symbolPrefix] = path)
export const get = path= > router({  path,  method: 'get'})Copy the code

conclusion

In general, the project is relatively simple, and there are many shortcomings. I will continue to improve the project. I hope that friends can put forward their shortcomings and suggestions. This is my first time to write an article, the level is limited, write a deep knowledge, had to take their own project as a maiden work 😂. I hope you can bear with me. Finally, if you feel the project is good, don’t be stingy with your star yo! Thank you very much!

GitHub project address