即將迎來的端午小假期,小夥伴們都準備好怎麼度過了麼?。我每次出去玩都避免不了去看場電影,這次藉此機會向大家介紹下我開發的可以檢視電影預告片的小專案,希望大家可以去測試,瀏覽一波即將上映的電影同時可以幫助我測試一下,指出不足,我都會虛心接受的呦!謝謝大家。
專案演示地址
效果圖
專案介紹
前端是通過vue-cli進行構建專案,後端介面是使用Koa進行編寫的。電影相關資料是使用puppeteer進行爬取並存在mongoDB資料庫中,為減輕頻寬壓力將預告片上傳到七牛雲上。其主要功能包括:
- 電影列表的展示
- 電影詳情資訊及預告片播放功能
- 根據上映情況、分類、評分進行篩選電影
- 電影熱度前十榜單
- 搜尋電影功能
- 使用者的註冊與登入。
未來想完善的功能:
- 對電影的收藏與喜歡
- 根據所在地推薦購票地點
- 使用者資訊相關的操作
- 電影資料的自動爬取更新
- 專案web端、小程式端
技術問題
電影上映狀態路由切換問題
電影上映狀態分為正在熱映與即將上映,其中list路由頁是通過引數進行轉換,1為正在上映,2為即將上映。路由配置如下:
{
path: '/movie', name: 'movie', component: Movie, children: [ {
path: 'all/:type', name: 'list', component: List
} ]
}複製程式碼
同路由元件引數切換不會再次觸發created
、mounted
生命週期函式,所以要實現引數切換重新請求資料需要在元件內導航守衛中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進行爬取得到的,獲取電影資料總共分為四步:
- 利用puppeteer模擬瀏覽器訪問豆瓣網站獲取電影的名字、海報、doubanId、評分存入資料庫。爬取網址是:
const nowUrl = 'https://movie.douban.com/cinema/nowplaying/beijing/'const comUrl = 'https://movie.douban.com/coming'複製程式碼
- 利用豆瓣提供的開放API,通過迴圈資料庫中電影doubanId來獲取到電影詳細的資訊,例如導演、演員、簡介、型別、上映日期等。
- 利用puppeteer瀏覽豆瓣電影詳情頁,從而跳轉到預告片頁面爬取預告片的資源,存入資料庫。爬取網址是:
const url = 'https://movie.douban.com/subject/'複製程式碼
- 使用七牛雲提供的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呦!謝謝!