案例介绍
前后端架构分离
接口设计
https://www.yuque.com/books/share/6eb0a508-d745-4e75-8631-8eb127b7b7ca?#
使用 Yapi 管理接口
项目初始化
npm i create-egg -g
create-egg youtube-clone-eggjs (smaple)
cd youtube-clone-eggjs
npm install
npm run dev
初始化mongoose配置
npm i egg-mongoose --save
/config/config.default.js
'use strict';
module.exports = appInfo => {
const config = exports = {};
config.keys = appInfo.name + '_1649334278876_9943';
config.middleware = [];
const userConfig = {
};
+ config.mongoose = {
client: {
url: 'mongodb://127.0.0.1/youtube-clone',
options: {},
plugins: [],
},
};
return {
...config,
...userConfig,
};
};
/config/plugin.js
'use strict';
+ exports.mongoose = {
enable: true,
package: 'egg-mongoose'
}
数据模型设计
module.exports = app => {
const mongoose = app.mongoose
const Schema = mongoose.Schema
const userSchema = new Schema({
username: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
select: false,
required: true
},
avatar: {
type: String,
default: null
},
cover: {
type: String,
default: null
},
channelDescription: {
type: String,
default: null
},
subscribersCount: {
type: Number,
default: 0
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
})
return mongoose.model('User', userSchema)
}
视频:
module.exports = app => {
const mongoose = app.mongoose
const Schema = mongoose.Schema
const videoSchema = new Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
vodVideoId: {
type: String,
required: true
},
cover: {
type: String,
required: true
},
user: {
type: mongoose.ObjectId,
required: true,
ref: 'User'
},
commentsCount: {
type: Number,
default: 0
},
dislikesCount: {
type: Number,
default: 0
},
likesCount: {
type: Number,
default: 0
},
viewsCount: {
type: Number,
default: 0
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
})
return mongoose.model('Video', videoSchema)
}
视频点赞:
module.exports = app => {
const mongoose = app.mongoose
const Schema = mongoose.Schema
const likeSchema = new Schema({
like: {
type: Number,
enum: [1, -1],
required: true
},
user: {
type: mongoose.ObjectId,
ref: 'User',
required: true
},
video: {
type: mongoose.ObjectId,
ref: 'Video',
required: true
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
})
return mongoose.model('VideoLike', likeSchema)
}
视频评论:
module.exports = app => {
const mongoose = app.mongoose
const Schema = mongoose.Schema
const commentSchema = new Schema({
content: {
type: String,
required: true
},
user: {
type: mongoose.ObjectId,
ref: 'User',
required: true
},
video: {
type: mongoose.ObjectId,
ref: 'Video',
required: true
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
})
return mongoose.model('Comment', commentSchema)
}
频道(用户)订阅
module.exports = app => {
const mongoose = app.mongoose
const Schema = mongoose.Schema
const subscriptionSchema = new Schema({
user: {
type: mongoose.ObjectId,
ref: 'User',
required: true
},
channel: {
type: mongoose.ObjectId,
ref: 'User',
required: true
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
})
return mongoose.model('Subscription', subscriptionSchema)
}
观看历史:
module.exports = app => {
const mongoose = app.mongoose
const Schema = mongoose.Schema
const viewSchema = new Schema({
user: {
type: mongoose.ObjectId,
ref: 'User',
required: true
},
video: {
type: mongoose.ObjectId,
ref: 'Video',
required: true
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
})
return mongoose.model('View', viewSchema)
}
用户注册-准备
app/router.js
'use strict';
module.exports = app => {
const { router, controller } = app;
router.prefix('/api/v1')
router.get('/users', controller.user.create)
};
app/controller/user.js
'use strict';
const Controller = require('egg').Controller;
class UserController extends Controller {
async create() {
const { ctx } = this;
ctx.body = 'UserController'
}
}
module.exports = UserController;
config/config.default.js
'use strict'
module.exports = appInfo => {
const config = {}
config.keys = appInfo.name + '_1611716016238_6422'
config.middleware = ['errorHandler']
const userConfig = {
}
config.mongoose = {
client: {
url: 'mongodb://127.0.0.1/youtube-clone',
options: {
useUnifiedTopology: true
},
plugins: []
}
}
+ config.security = {
csrf: {
enable: false
}
}
return {
...config,
...userConfig
}
}
用户注册-数据验证介绍
参数校验 :https://www.eggjs.org/zh-CN/basics/controller#%E5%8F%82%E6%95%B0%E6%A0%A1%E9%AA%8C
https://github.com/eggjs/egg-validate
用户注册-数据验证
npm i egg-validate --save
exports.validate = {
enable: true,
package: 'egg-validate',
};
'use strict';
const Controller = require('egg').Controller;
class UserController extends Controller {
async create() {
const { ctx } = this;
+ this.ctx.validate({
username: { type: string },
email: { type: email },
})
}
}
module.exports = UserController;
用户注册-自定义异常处理
2种方式 1.try catch 2.框架层统一处理异常:https://www.eggjs.org/zh-CN/core/error-handling 或 https://www.eggjs.org/zh-CN/tutorials/restful#%E7%BB%9F%E4%B8%80%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86
通一错误处理
module.exports = () => {
return async function errorHandler(ctx, next) {
try {
await next();
} catch (err) {
ctx.app.emit('error', err, ctx);
const status = err.status || 500;
const error =
status === 500 && ctx.app.config.env === 'prod'
? 'Internal Server Error'
: err.message;
ctx.body = { error };
if (status === 422) {
ctx.body.detail = err.errors;
}
ctx.status = status;
}
};
};
config/config.default.js
'use strict';
module.exports = appInfo => {
+
+ config.middleware = ['errorHandler'];
return {
...config,
...userConfig,
};
};
'use strict';
const Controller = require('egg').Controller;
class UserController extends Controller {
async create() {
const { ctx } = this;
this.ctx.validate({
username: { type: string },
email: { type: email },
password: { type: string },
})
}
}
module.exports = UserController;
用户注册-将数据保存到数据库
'use strict';
const Controller = require('egg').Controller;
class UserController extends Controller {
async index() {
const { ctx } = this;
const body = this.ctx.request.body
this.ctx.validate({
username: { type: string },
email: { type: email },
password: { type: string },
})
if(await this.service.user.findByUsername(body.username)) {
this.ctx.throw(422, '用户已经存在')
}
if(await this.service.user.findByEmail(body.username)) {
this.ctx.throw(422, '邮箱已经存在')
}
const user = await this.service.user.createUser(body)
this.ctx.body = {
user: {
email: user.email,
username: user.username,
avator: user.username,
}
}
ctx.body = 'UserController'
}
}
module.exports = UserController;
service/user.js 专门处理数据库的操作
const Service = require('err').Service
class UserService extends Service {
get User() {
return this.app.model.User
}
findByUsername(username) {
return this.User.findOne({ username })
}
findByEmail(email) {
return this.User.findOne({ email })
}
async createUser(data) {
data.password = this.ctx.helper.md5(data.password)
const user = await this.User(data)
await user.save()
return user
}
}
app/entend/htlper.js
const crypto = require('crypto')
const _ = require('lodash')
exports.md5 = str => {
return crypto.createHash('md5').update(str).digest('hex')
}
exports._ = _
用户注册-处理Token
npm i jsonwebtoken
config/config.default.js
'use strict'
module.exports = appInfo => {
+ config.jwt = {
secret: 'a6e8561e-58df-4715-aa21-b5d1a091e71a',
expiresIn: '1d'
}
return {
...config,
...userConfig
}
}
service/user.js
const Service = require('err').Service
+ const jwt = require('jsonwebtoken')
class UserService extends Service {
get User() {
return this.app.model.User
}
findByUsername(username) {
return this.User.findOne({ username })
}
findByEmail(email) {
return this.User.findOne({ email })
}
async createUser(data) {
data.password = this.ctx.helper.md5(data.password)
const user = await this.User(data)
await user.save()
return user
}
+ createToken (data) {
const token = jwt.sign(data, this.app.config.jwt.secret, {
expiresIn: this.app.config.jwt.expiresIn
})
return token
}
}
controller/user.js
'use strict';
const Controller = require('egg').Controller;
class UserController extends Controller {
async create() {
const { ctx } = this;
const body = this.ctx.request.body
this.ctx.validate({
username: { type: string },
email: { type: email },
password: { type: string },
})
const userService = this.service.user
if(await userService.findByUsername(body.username)) {
this.ctx.throw(422, '用户已经存在')
}
if(await userService.findByEmail(body.username)) {
this.ctx.throw(422, '邮箱已经存在')
}
const user = await userService.createUser(body)
+ const token = await userService.createToken({ userId: user._id })
this.ctx.body = {
user: {
+ token,
email: user.email,
username: user.username,
avator: user.username,
}
}
}
}
module.exports = UserController;
用户登录
获取当前登录用户
router.js
'use strict';
module.exports = app => {
const { router, controller } = app;
+ const auth = app.middleware.auth()
router.prefix('/api/v1')
router.post('/users', controller.user.index)
router.get('/users/login', controller.user.login)
+ router.get('/user', auth, controller.user.getCurrentUser)
};
middleware/auth.js
module.exports = () => {
return async (ctx, next) => {
const token = ctx.headers['authorzation']
token = token ? ctx.headers['authorzation'].split('Bearer ')[1] : null
if (!token) {
ctx.throw(401)
}
try {
const data = ctx.service.user.veriftToken(token)
ctx.user = ctx.module.User.findById(data.user)
} catch (error) {
ctx.throw(error)
}
await next()
}
}
controller/user.js
'use strict';
const Controller = require('egg').Controller;
class UserController extends Controller {
+ async getCurrentUser() {
const user = this.ctx.user
this.ctx.body = {
user: {
email: user.email,
token: this.ctx.headers['authorzation'],
username: user.username,
avator: user.avator,
}
}
}
}
module.exports = UserController;
service/user.js
const Service = require('err').Service
const jwt = require('jsonwebtoken')
class UserService extends Service {
get User() {
return this.app.model.User
}
findByUsername(username) {
return this.User.findOne({ username })
}
findByEmail(email) {
return this.User.findOne({ email })
}
async createUser(data) {
data.password = this.ctx.helper.md5(data.password)
const user = await this.User(data)
await user.save()
return user
}
createToken (data) {
const token = jwt.sign(data, this.app.config.jwt.secret, {
expiresIn: this.app.config.jwt.expiresIn
})
return token
}
+ veriftToken(token) {
return jwt.verify(token, this.app.config.jwt.secret)
}
}
更新当前登录用户资料
router.js
'use strict';
module.exports = app => {
const { router, controller } = app;
const auth = app.middleware.auth()
router.prefix('/api/v1')
router.post('/users', controller.user.index)
router.get('/users/login', controller.user.login)
router.get('/user', auth, controller.user.getCurrentUser)
+ router.patch('/user', auth, controller.user.update)
};
service/user.js
const Service = require('err').Service
const jwt = require('jsonwebtoken')
class UserService extends Service {
+ updaetUser(data) {
return this.User.findByIdAndUpdate(this.ctx.user._id, data, { new: true })
}
}
controller/user.js
'use strict';
const Controller = require('egg').Controller;
class UserController extends Controller {
+ async update() {
const body = this.ctx.request.body
this.ctx.validate({
username: { type: 'string', required: false },
email: { type: 'email', required: false },
password: { type: 'string', required: false},
})
this.userService = this.service.user
if (body.username) {
if (body.username !== this.ctx.user.username && await userService.findByUsername(body.username)) {
this.ctx.throw(422, '用户已经存在')
}
}
if (body.password) {
body.password = this.ctx.header.md5(password)
}
if (body.email) {
if (body.email !== this.ctx.user.email && await userService.findByUsername(body.email)) {
this.ctx.throw(422, '邮箱已经存在')
}
}
const user = await userService.updaetUser(body)
this.ctx.body = {
user: {
email: user.email,
username: user.username,
avator: user.avator,
}
}
}
}
module.exports = UserController;
订阅频道
router.js
'use strict';
module.exports = app => {
const { router, controller } = app;
const auth = app.middleware.auth()
router.prefix('/api/v1')
router.post('/users', controller.user.index)
router.get('/users/login', controller.user.login)
router.get('/user', auth, controller.user.getCurrentUser)
router.patch('/user', auth, controller.user.update)
+ router.post('/users/:userId/subscribe', auth, controller.user.subscribe)
};
service/user.js
const Service = require('err').Service
const jwt = require('jsonwebtoken')
class UserService extends Service {
+ async subscribe(channelId, userId) {
const { Subscription } = this.app.model
const record = Subscription.findOne({ user: userId, channelId: channelId })
const user = User.findById(channelId)
if (!record) {
await new Subscription({ user: userId, channelId: channelId }).save()
}
user.subscribesCount++
await user.save()
return user
}
}
controller/user.js
'use strict';
const Controller = require('egg').Controller;
class UserController extends Controller {
+ async subscribe() {
const userId = this.ctx.user._id
const channelId = this.ctx.params.userId
if(userId.equals(channelId)) {
this.ctx.throw(422, '用户不能订阅自己')
}
const user = await this.service.user.subscribe(userId, channelId)
this.ctx.body = {
user: {
...user.toJson()
isSubscribe: true,
}
}
}
}
module.exports = UserController;
使用lodash-pick处理返回的数据
npm install lodash
extend/helper.js
const crypto = require('crypto')
+ const _ = require('lodash')
exports.md5 = str => {
return crypto.createHash('md5').update(str).digest('hex')
}
+ exports._ = _
controller/user.js
'use strict';
const Controller = require('egg').Controller;
class UserController extends Controller {
async subscribe() {
const userId = this.ctx.user._id
const channelId = this.ctx.params.userId
if(userId.equals(channelId)) {
this.ctx.throw(422, '用户不能订阅自己')
}
const user = await this.service.user.subscribe(userId, channelId)
this.ctx.body = {
user: {
+ ...this.ctx.helper._.pick(user, ['username', 'email', 'avator', 'cover', 'channelDescription', 'subscribersCount']),
isSubscribe: true,
}
}
}
}
module.exports = UserController;
消订阅频道
router.js
'use strict';
module.exports = app => {
const { router, controller } = app;
const auth = app.middleware.auth()
router.prefix('/api/v1')
router.post('/users', controller.user.index)
router.get('/users/login', controller.user.login)
router.get('/user', auth, controller.user.getCurrentUser)
router.patch('/user', auth, controller.user.update)
router.post('/users/:userId/subscribe', auth, controller.user.subscribe)
+ router.delete('/users/:userId/subscribe', auth, controller.user.unsubscribe)
};
service/user.js
const Service = require('err').Service
const jwt = require('jsonwebtoken')
class UserService extends Service {
+ async unsubscribe(channelId, userId) {
const { Subscription } = this.app.model
const record = Subscription.findOne({ user: userId, channelId: channelId })
const user = User.findById(channelId)
if (record) {
await record.remove()
}
user.subscribesCount--
await user.save()
return user
}
}
controller/user.js
'use strict';
const Controller = require('egg').Controller;
class UserController extends Controller {
+ async unsubscribe() {
const userId = this.ctx.user._id
const channelId = this.ctx.params.userId
if(userId.equals(channelId)) {
this.ctx.throw(422, '用户不能订阅自己')
}
const user = await this.service.user.unsubscribe(userId, channelId)
this.ctx.body = {
user: {
...this.ctx.helper._.pick(user, ['username', 'email', 'avator', 'cover', 'channelDescription', 'subscribersCount']),
isSubscribe: false,
}
}
}
}
module.exports = UserController;
获取用户资料
router.js
'use strict';
module.exports = app => {
const { router, controller } = app;
const auth = app.middleware.auth()
router.prefix('/api/v1')
router.post('/users', controller.user.index)
router.get('/users/login', controller.user.login)
router.get('/user', auth, controller.user.getCurrentUser)
router.patch('/user', auth, controller.user.update)
router.post('/users/:userId/subscribe', auth, controller.user.subscribe)
router.delete('/users/:userId/subscribe', auth, controller.user.unsubscribe)
+ router.get('/user/:userid', app.middleware.auth({ require: true }), controller.user.getUser)
};
middleware/auth.js
+ module.exports = (options = { require: true }) => {
return async (ctx, next) => {
const token = ctx.headers['authorzation']
token = token ? ctx.headers['authorzation'].split('Bearer ')[1] : null
if (!token) {
ctx.throw(401)
}
+ if (token) {
try {
const data = ctx.service.user.veriftToken(token)
ctx.user = ctx.module.User.findById(data.user)
} catch (error) {
ctx.throw(error)
}
+ } else if (options.required) {
ctx.throw(401)
}
await next()
}
}
controller/user.js
async getUser() {
let isSubscribe = false
if (this.ctx.user) {
const record = await this.app.model.Subscription({
user: this.ctx.user._id,
cancel: this.ctx.params.userId
})
if (record) {
isSubscribe = true
}
}
const user = await this.app.model.User.findById(this.ctx.params.userId)
this.ctx.body = {
...this.ctx.helper._.pick(user, ['username', 'email', 'avator', 'cover', 'channelDescription', 'subscribersCount']),
}
}
获取用户订阅的频道列表
router.js
router.get('/users/:userId/subscriptions', auth, controller.user.getSubscriptions)
controller/user.js
async getSubscriptions() {
const Subscription = this.app.model.Subscription
const subscription = await Subscription.find({ user: this.ctx.params.userId }).populate('channel')
subscription = subscription.map (item => {
return this.ctx.helper._.pick(item.channel, ['_id', 'username', 'avtor'])
})
this.ctx.body = {
subscription
}
}
阿里云视频点播服务介绍
视频上传-获取上传地址和凭证
npm i @alicloud/pop-core --save
router.js
router.get('/vod/CreateUploadVideo', auth, controller.vod.createUploadvideo)
controller/vod.js
'use strict';
const Controller = require('egg').Controller;
const RPCClient = require('@alicloud/pop-core').RPCClient
function initVodClient (accessKeyId, accessKeySecret) {
const regionId = 'cn-shanghai'
const client = new RPCClient({
accessKeyId: accessKeyId,
accessKeySecret: accessKeySecret,
endpoint: 'http://vod.' + regionId + '.aliyuncs.com',
apiVersion: '2017-03-21'
})
class VodController extends Controller {
async createUploadvideo() {
const query = this.ctx.query
this.ctx.validate({ title: { type: 'string' }, FilName: { type: 'string' } })
const vodClient = initVodClient('LTAI4FzgRRRN2MjwBzc3xQtp', 'xAllGuORtBDVcrTQpTOWu4HfjYgN1p')
this.ctx.body = await vodClient.request('CreateUploadVideo',query, {})
}
}
module.exports = VodController;
视频上传-上传完成
先解决跨域的问题。 npm i egg-cors --save
plugin.js
+ exports.cors = {
enable: true,
package: 'egg-cors'
}
config.default.js
+ config.cors = {
origin: '*'
}
视频上传-刷新视频上传凭证
extend/application.js
const RPCClient = require('@alicloud/pop-core').RPCClient
function initVodClient (accessKeyId, accessKeySecret) {
const regionId = 'cn-shanghai'
const client = new RPCClient({
accessKeyId: accessKeyId,
accessKeySecret: accessKeySecret,
endpoint: 'http://vod.' + regionId + '.aliyuncs.com',
apiVersion: '2017-03-21'
})
return client
}
let vodClient = null
module.exports = {
get vodClient () {
if (!vodClient) {
vodClient = initVodClient('LTAI4FzgRRRN2MjwBzc3xQtp', 'xAllGuORtBDVcrTQpTOWu4HfjYgN1p')
}
return vodClient
}
}
conroller/vod.js
'use strict';
const Controller = require('egg').Controller;
class VodController extends Controller {
async createUploadVideo() {
const query = this.ctx.query
this.ctx.validate({ title: { type: 'string' }, FilName: { type: 'string' } }, query)
+ this.ctx.body = await this.app.vodClient.request('CreateUploadVideo', query, {})
}
+ async refreshUploadvideo() {
const query = this.ctx.query
this.ctx.validate({ VideoId: { type: 'string' } }, query)
this.ctx.body = await this.app.vodClient.request('RefreshUploadvideo', query, {})
}
}
module.exports = VodController;
视频上传-优化配置信息
config/config.local.js 只针对本地开发的文件
const secret = require('./secret')
exports.vod = {
...secret.vod
}
ignore.js
exports.vod = {
accessKeyId: 'LTAI4FzgRRRN2MjwBzc3xQtp',
accessKeySecret: 'xAllGuORtBDVcrTQpTOWu4HfjYgN1p'
}
config/config.prod.js 只针对生产环境的文件
exports.vod = {
accessKeyId: process.env.accessKeyId,
accessKeySecret: process.env.accessKeySecret
}
extend/application.js
const RPCClient = require('@alicloud/pop-core').RPCClient
function initVodClient (accessKeyId, accessKeySecret) {
const regionId = 'cn-shanghai'
const client = new RPCClient({
accessKeyId: accessKeyId,
accessKeySecret: accessKeySecret,
endpoint: 'http://vod.' + regionId + '.aliyuncs.com',
apiVersion: '2017-03-21'
})
return client
}
let vodClient = null
module.exports = {
get vodClient () {
if (!vodClient) {
+ const { accessKeyId, accessKeySecret } = this.config.vod
+ vodClient = initVodClient(accessKeyId, accessKeySecret)
}
return vodClient
}
}
创建视频-接口实现
router.post('/videos', auth, controller.vod.createVideo)
controller/video.js
const Controller = require('egg').Controller
class VideoController extends Controller {
async createVideo () {
const body = this.ctx.request.body
const { Video } = this.app.model
this.ctx.validate({
title: { type: 'string' },
description: { type: 'string' },
vodVideoId: { type: 'string' }
}, body)
body.cover = 'http://vod.lipengzhou.com/image/default/A806D6D6B0FD4D118F1C824748826104-6-2.png'
body.user = this.ctx.user._id
const video = await new Video(body).save()
this.ctx.status = 201
this.ctx.body = {
video
}
const setVideoCover = async (video) => {
const vodVideoInfo = await this.app.vodClient.request('GetVideoInfo', {
VideoId: video.vodVideoId
})
if (vodVideoInfo.Video.CoverURL) {
video.cover = vodVideoInfo.Video.CoverURL
await video.save()
} else {
await new Promise(resolve => {
setTimeout(() => {
resolve()
}, 3000)
})
await setVideoCover(video)
}
}
setVideoCover(video)
}
}
module.exports = VideoController
获取视频详情-接口实现
router.get('/videos/:videoId', auth, controller.vod.getVideo)
controller/Video.js
async getVideo() {
const { Video, VideoLike, Subscription } = this.app.model
const { videoId } = this.ctx.params
let video = await Video.findById(videoId).populate('user', '_id username avatar subscribersCount')
if (!video) {
this.ctx.throw(404, 'Video Not Found')
}
video = video.toJSON()
video.isLiked = false
video.isDisliked = false
video.user.isSubscribed = false
if(this.ctx.user) {
const userId = this.ctx.user._id
if (await VideoLike.findOne({ user: userId, video: videoId, like: 1 })) {
video.isLiked = true
}
if (await VideoLike.findOne({ user: userId, video: videoId, like: -1 })) {
video.isDisliked = true
}
if (await Subscription.findOne({ user: userId, channel: video.user._id })) {
video.user.isSubscribed = true
}
}
this.ctx.body = {
video
}
}
获取视频列表-接口实现
router.js
router.get('/videos', controller.vod.getVideos)
controller/video.js
async getVideos() {
const { Video } this.app.model
const { pageNum = 1, pageSize = 10 } = this.ctx.query
pageNum = Number.parseInt(pageNum)
pageSize = Number.parseInt(pageSize)
const getVideos = Video.find().popupate('user')
.sort({ createdAt: -1 })
.skip((pageNum -1)) * pageSize)
.limit(pageSize)
const getVideosCount = Video.countDocuments()
const [videos, videosCount] = await Promise.all([
getVideos,
getVideosCount
])
this.ctx.body = {
videos,
videosCount
}
}
获取用户发布的视频列表-接口实现
router.js
router.get('/users/:userId/videos', controller.video.getUserVideos)
controller/video.js
async getUserVideos () {
const { Video } = this.app.model
let { pageNum = 1, pageSize = 10 } = this.ctx.query
const userId = this.ctx.params.userId
pageNum = Number.parseInt(pageNum)
pageSize = Number.parseInt(pageSize)
const getVideos = Video
.find({
user: userId
})
.populate('user')
.sort({
createdAt: -1
})
.skip((pageNum - 1) * pageSize)
.limit(pageSize)
const getVideosCount = Video.countDocuments({
user: userId
})
const [videos, videosCount] = await Promise.all([
getVideos,
getVideosCount
])
this.ctx.body = {
videos,
videosCount
}
}
获取用户关注的频道视频列表-接口实现
router.js
router.get('/user/videos/feed', auth, controller.video.getUserFeedVideos)
controller/video.js
async getUserFeedVideos () {
const { Video, Subscription } = this.app.model
let { pageNum = 1, pageSize = 10 } = this.ctx.query
const userId = this.ctx.user._id
pageNum = Number.parseInt(pageNum)
pageSize = Number.parseInt(pageSize)
const channels = await Subscription.find({ user: userId }).populate('channel')
const getVideos = Video
.find({
user: {
$in: channels.map(item => item.channel._id)
}
})
.populate('user')
.sort({
createdAt: -1
})
.skip((pageNum - 1) * pageSize)
.limit(pageSize)
const getVideosCount = Video.countDocuments({
user: {
$in: channels.map(item => item.channel._id)
}
})
const [videos, videosCount] = await Promise.all([
getVideos,
getVideosCount
])
this.ctx.body = {
videos,
videosCount
}
}
修改视频-接口实现
router.js
router.patch('/videos/:videoId', auth, controller.video.updateVideo)
controller/video.js
async updateVideo () {
const { body } = this.ctx.request
const { Video } = this.app.model
const { videoId } = this.ctx.params
const userId = this.ctx.user._id
this.ctx.validate({
title: { type: 'string', required: false },
description: { type: 'string', required: false },
vodVideoId: { type: 'string', required: false },
cover: { type: 'string', required: false }
})
const video = await Video.findById(videoId)
if (!video) {
this.ctx.throw(404, 'Video Not Found')
}
if (!video.user.equals(userId)) {
this.ctx.throw(403)
}
Object.assign(video, this.ctx.helper._.pick(body, ['title', 'description', 'vodVideoId', 'cover']))
await video.save()
this.ctx.body = {
video
}
}
删除视频-接口实现
router.js
router.delete('/videos/:videoId', auth, controller.video.deleteVideo)
controller/video.js
async deleteVideo () {
const { Video } = this.app.model
const { videoId } = this.ctx.params
const video = await Video.findById(videoId)
if (!video) {
this.ctx.throw(404)
}
if (!video.user.equals(this.ctx.user._id)) {
this.ctx.throw(403)
}
await video.remove()
this.ctx.status = 204
}
添加视频评论-接口实现
router.js
router.post('/videos/:videoId/comments', auth, controller.video.createComment)
controller/video.js
async createComment () {
const body = this.ctx.request.body
const { Video, VideoComment } = this.app.model
const { videoId } = this.ctx.params
this.ctx.validate({
content: 'string'
}, body)
const video = await Video.findById(videoId)
if (!video) {
this.ctx.throw(404)
}
const comment = await new VideoComment({
content: body.content,
user: this.ctx.user._id,
video: videoId
}).save()
video.commentsCount = await VideoComment.countDocuments({
video: videoId
})
await video.save()
await comment.populate('user').populate('video').execPopulate()
this.ctx.body = {
comment
}
}
获取视频评论列表-接口实现
router.js
router.get('/videos/:videoId/comments', controller.video.getVideoComments)
controller/video.js
async getVideoComments () {
const { videoId } = this.ctx.params
const { VideoComment } = this.app.model
let { pageNum = 1, pageSize = 10 } = this.ctx.query
pageNum = Number.parseInt(pageNum)
pageSize = Number.parseInt(pageSize)
const getComments = VideoComment
.find({
video: videoId
})
.skip((pageNum - 1) * pageSize)
.limit(pageSize)
.populate('user')
.populate('video')
const getCommentsCount = VideoComment.countDocuments({
video: videoId
})
const [comments, commentsCount] = await Promise.all([
getComments,
getCommentsCount
])
this.ctx.body = {
comments,
commentsCount
}
}
删除视频评论-接口实现
router.js
router.delete('/videos/:videoId/comments/:commentId', auth, controller.video.deleteVideoComment)
controller/video.js
async deleteVideoComment () {
const { Video, VideoComment } = this.app.model
const { videoId, commentId } = this.ctx.params
const video = await Video.findById(videoId)
if (!video) {
this.ctx.throw(404, 'Video Not Found')
}
const comment = await VideoComment.findById(commentId)
if (!comment) {
this.ctx.throw(404, 'Comment Not Found')
}
if (!comment.user.equals(this.ctx.user._id)) {
this.ctx.throw(403)
}
await comment.remove()
video.commentsCount = await VideoComment.countDocuments({
video: videoId
})
await video.save()
this.ctx.status = 204
}
喜欢视频和不喜欢视频-接口实现
router.js
router.post('/videos/:videoId/like', auth, controller.video.likeVideo)
router.post('/videos/:videoId/dislike', auth, controller.video.dislikeVideo)
controller/video.js
async likeVideo () {
const { Video, VideoLike } = this.app.model
const { videoId } = this.ctx.params
const userId = this.ctx.user._id
const video = await Video.findById(videoId)
if (!video) {
this.ctx.throw(404, 'Video Not Found')
}
const doc = await VideoLike.findOne({
user: userId,
video: videoId
})
let isLiked = true
if (doc && doc.like === 1) {
await doc.remove()
isLiked = false
} else if (doc && doc.like === -1) {
doc.like = 1
await doc.save()
} else {
await new VideoLike({
user: userId,
video: videoId,
like: 1
}).save()
}
video.likesCount = await VideoLike.countDocuments({
video: videoId,
like: 1
})
video.dislikesCount = await VideoLike.countDocuments({
video: videoId,
like: -1
})
await video.save()
this.ctx.body = {
video: {
...video.toJSON(),
isLiked
}
}
}
async dislikeVideo () {
const { Video, VideoLike } = this.app.model
const { videoId } = this.ctx.params
const userId = this.ctx.user._id
const video = await Video.findById(videoId)
if (!video) {
this.ctx.throw(404, `No video found for ID - ${videoId}`)
}
const doc = await VideoLike.findOne({
user: userId,
video: videoId
})
let isDisliked = true
if (doc && doc.like === -1) {
await doc.remove()
isDisliked = false
} else if (doc && doc.like === 1) {
doc.like = -1
await doc.save()
} else {
await new VideoLike({
user: userId,
video: videoId,
like: -1
}).save()
}
video.likesCount = await VideoLike.countDocuments({
video: videoId,
like: 1
})
video.dislikesCount = await VideoLike.countDocuments({
video: videoId,
like: -1
})
this.ctx.body = {
video: {
...video.toJSON(),
isDisliked
}
}
}
|