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页面、搜索页面、筛选页面、榜单页面等均有使用到。其主要效果如下图:

但当在榜单页面时所有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介绍
原文发布时间:2018-06-15原文作者:lihaozecq本文来源掘金如需转载请紧急联系作者
上一篇:什么是Oracle事务?这是我见过最好的答案


下一篇:Word中正文两栏表格通栏排版