频道创建及用户信息修改操作
用户 Schema 新增字段
model\userModel.js
javascript
const mongoose = require('mongoose');
const md5 = require('../util/md5');
const baseSchema = require('./baseModel');
const userSchema = new mongoose.Schema({
cover: {
type: String,
default: null,
},
channeldes: {
type: String,
default: null,
},
...baseSchema,
});
module.exports = userSchema;
router\user.js
新增更新用户路由
javascript
const validator = require('../middleware/validator/userValidator');
// 更新用户信息
router.put('/', verifyToken, validator.update, userConroller.update);
middleware\validator\userValidator.js
添加更新用户信息参数校验
javascript
// 更新用户信息校验规则
module.exports.update = validate([
body('username')
.notEmpty()
.withMessage('用户名不能为空')
.bail()
.isLength({ min: 3 })
.withMessage('用户名长度最小为3')
.bail()
.custom((value) => {
return User.findOne({ username: value }).then((user) => {
if (user) {
return Promise.reject('此用户已注册');
}
});
})
.bail(),
body('email')
.notEmpty()
.withMessage('邮箱不能为空')
.bail()
.isEmail()
.withMessage('邮箱格式不正确')
.bail()
.custom((value) => {
return User.findOne({ email: value }).then((user) => {
if (user) {
return Promise.reject('邮箱已注册');
}
});
})
.bail(),
body('phone')
.notEmpty()
.withMessage('手机不能为空')
.bail()
.custom((value) => {
return User.findOne({ phone: value }).then((user) => {
if (user) {
return Promise.reject('手机号已注册');
}
});
})
.bail(),
]);
controller\user.js
处理更新用户信息业务逻辑
javascript
// 更新用户信息
const update = async (req, res) => {
try {
// 更新用户信息
const id = req.user.user;
const dbBack = await User.findByIdAndUpdate(id, req.body, {
new: true,
});
res.status(200).json(dbBack);
} catch (error) {
res.status(400).json(error.message);
}
};

文件上传操作
安装 multer 文件上传库
javascript
pnpm i multer@1.4.4 -S
router\user.js
在上传用户头像路由添加上传图片中间件
javascript
const multer = require('multer');
const upload = multer({ dest: 'public/' });
// 上传头像
router.post(
'/headimg',
verifyToken,
upload.single('headimg'),
userConroller.upload
);
controller\user.js
处理上传图片后的处理逻辑
javascript
// 上传用户头像
const upload = async (req, res) => {
try {
const fileArr = req.file.originalname.split('.');
const fileType = fileArr[fileArr.length - 1];
const oldPath = `./public/${req.file.filename}`;
const newPath = `./public/${req.file.filename}.${fileType}`;
await rename(oldPath, newPath);
res.status(200).json(newPath);
} catch (error) {
res.status(400).json(error.message);
}
};

访问用户头像

VoD视频点播服务
安装包
pnpm i @alicloud/pop-core -S
controller\vodController.js
使用安装包提供的服务获取上传凭证信息
javascript
var RPCClient = require('@alicloud/pop-core').RPCClient;
// 初始化点播客户端
function initVodClient(accessKeyId, accessKeySecret) {
const regionId = 'cn-shanghai'; // 点播服务接入区域
var client = new RPCClient({
accessKeyId: accessKeyId,
accessKeySecret: accessKeySecret,
endpoint: 'vod.' + regionId + '.aliyuncs.com',
apiVersion: '2017-03-21',
});
return client;
}
// 获取点播视频上传地址和凭证
exports.getVod = async (req, res) => {
var client = initVodClient('LTAI5t3Yxxxxxx', 'xxxxxxxxxxxxxxxxxxxxxx');
const vodBack = await client.request('CreateUploadVideo', {
Title: 'xxxxxxxxxxxxxx',
FileName: 'xxxxxxxxxxxxxx.mp4',
});
res.status(200).json({ vod: vodBack });
};
router\video.js
定义获取视频上传凭证路由
javascript
// 获取点播视频上传地址和凭证
router.get('getVod', vodController.getVod);
客户端上传VoD
https://blog.csdn.net/u014494148/article/details/132144301
视频信息入库操作
model\videoModel.js
定义视频 video Schema
javascript
const mongoose = require('mongoose');
const baseSchema = require('./baseModel');
const videoSchema = new mongoose.Schema({
title: {
type: String,
required: true,
},
descrption: {
type: String,
required: true,
},
vodvideoId: {
type: String,
required: true,
},
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User',
},
cover: {
type: String,
required: true,
},
...baseSchema,
});
module.exports = videoSchema;
model\index.js
注册 videoModel
javascript
module.exports = {
User: mongoose.model('User', require('./userModel')),
Video: mongoose.model('Video', require('./videoModel')),
};
middleware\validator\videoValidator.js
路由参数校验
javascript
const { body } = require('express-validator');
const validate = require('./errorBack');
// const { User } = require('../../model');
// 上传视频验证规则
module.exports.videoValidator = validate([
body('title')
.notEmpty()
.withMessage('视频名称不能为空')
.bail()
.isLength({ max: 20 })
.withMessage('视频名长度不能超过20')
.bail(),
body('vodvideoId').notEmpty().withMessage('Vod不能为空').bail(),
]);
controller\videoController.js
处理视频上传业务逻辑
javascript
const { Video } = require('../model');
// 上传视频
exports.createVideo = async (req, res) => {
var body = req.body;
body.user = req.user._id;
const videoModel = new Video(body);
try {
var dbBack = await videoModel.save();
res.status(201).send({
code: 201,
message: '视频上传成功',
data: dbBack,
});
} catch (error) {
res.status(500).send({
code: 500,
message: '视频上传失败',
data: error,
});
}
};
router\video.js
定义上传视频路由
javascript
const express = require('express');
const router = express.Router();
const { verifyToken } = require('../util/jwt');
const vodController = require('../controller/vodController');
const videoController = require('../controller/videoController');
const { videoValidator } = require('../middleware/validator/videoValidator');
// 获取点播视频上传地址和凭证
router.get('getVod', vodController.getVod);
router.get(
'createvideo',
verifyToken,
videoValidator,
videoController.createVideo
);
module.exports = router;

视频列表及分页展示
router\video.js
定义视频列表路由
javascript
router.get('/videolist', verifyToken, videoController.videolist);
controller\videoController.js
处理视频分页列表逻辑
javascript
// 视频分页列表
exports.videolist = async (req, res) => {
var { page = 1, size = 10 } = req.query;
page = parseInt(page);
size = parseInt(size);
try {
var dbBack = await Video.find()
.skip((page - 1) * size)
.limit(size)
.sort({
createdAt: -1,
})
.populate('user');
// 视频总条数
var total = await Video.countDocuments();
res.status(200).send({
code: 200,
message: '视频列表获取成功',
total: total,
data: dbBack,
});
} catch (error) {
res.status(500).send({
code: 500,
message: '视频列表获取失败',
data: error,
});
}
};

获取视频详情
util\jwt.js
重新修改验证,使其能够自定义是否验证 token
javascript
/**
* @description 验证 token 中间件
* @param {*} required 是否需要验证 token, 默认不验证
* @returns
*/
module.exports.verifyToken = function (required = false) {
return async (req, res, next) => {
//从 header 读取 token, 并解析 token
const authorization = req.headers.authorization;
const token = authorization ? authorization.split('Bearer ')[1] : null;
if (token) {
try {
const user = await verifyJwt(token, uuid);
// 将用户信息存储到 req.user 中
req.user = user;
next();
} catch (error) {
res.status(401).send('token 验证失败');
}
} else if (required) {
res.status(401).send('缺少 token');
} else {
next();
}
};
};
router\video.js
定义获取视频详情路由
javascript
// 视频详情
router.get('/video/:videoId', verifyToken(false), videoController.videodetail);
controller\videoController.js
处理获取视频详情逻辑
javascript
// 获取视频详情
exports.videodetail = async (req, res) => {
var { videoId } = req.params;
try {
var dbBack = await Video.findById(videoId).populate('user');
if (dbBack) {
res.status(200).send({
code: 200,
message: '视频详情获取成功',
data: dbBack,
});
} else {
res.status(404).send({
code: 404,
message: '视频不存在',
});
}
} catch (error) {
res.status(500).send({
code: 500,
message: '视频详情获取失败',
data: error,
});
}
};

频道订阅与取消订阅
频道订阅
router\user.js
定义频道关注路由
javascript
// 关注用户
router.get('/follow/:userId', verifyToken(), userConroller.follow);
controller\user.js
实现频道订阅逻辑
javascript
// 关注用户
const follow = async (req, res) => {
try {
const userId = req.user.user._id;
const followId = req.params.userId;
// 不能关注自己
if (userId === followId) {
return res.status(400).json('不能关注自己');
}
// 是否已关注
const existingFollow = await Follow.findOne({
user: userId,
channle: followId,
});
if (existingFollow) {
return res.status(400).json('已关注该用户');
} else {
// 创建关注记录
const followModel = new Follow({
user: userId,
channle: followId,
});
await followModel.save();
// 更新被关注用户的粉丝数
await User.findByIdAndUpdate(followId, { $inc: { followCount: 1 } });
res.status(201).json('关注成功');
}
} catch (error) {
res.status(400).json(error.message);
}
};
model\followModel.js
定义频道关注关系 Model
javascript
const mongoose = require('mongoose');
const baseSchema = require('./baseModel');
const followSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User',
},
channle: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User',
},
...baseSchema,
});
module.exports = followSchema;
model\userModel.js
用户 model 新增粉丝数字段
javascript
// 粉丝数
followCount: {
type: Number,
default: 0,
},

取消订阅
router\user.js
定义取消订阅路由
javascript
// 取消关注用户
router.get('/unfollow/:userId', verifyToken(), userConroller.unfollow);
controller\user.js
处理取消订阅逻辑
javascript
// 取消关注用户
const unfollow = async (req, res) => {
try {
const userId = req.user.user._id;
const followId = req.params.userId;
// 不能关注自己
if (userId === followId) {
return res.status(400).json('不能取消关注自己');
}
// 是否已关注
const existingFollow = await Follow.findOne({
user: userId,
channle: followId,
});
if (existingFollow) {
// 删除关注记录
await Follow.deleteOne({ _id: existingFollow._id });
// 更新被关注用户的粉丝数
await User.findByIdAndUpdate(followId, { $inc: { followCount: -1 } });
res.status(200).json('取消关注成功');
} else {
res.status(400).json('未关注该用户');
}
} catch (error) {
res.status(400).json(error.message);
}
};

获取频道信息
router\user.js
定义获取用户详情路由
javascript
// 获取用户信息
router.get('/:userId', verifyToken(false), userConroller.getUserInfo);
controller\user.js
实现获取用户详情逻辑
javascript
// 获取用户信息
const getUserInfo = async (req, res) => {
try {
// 是否关注此用户
var isFlow = false;
// 已经登录了
if (req.user) {
// 是否关注此用户
const existingFollow = await Follow.findOne({
user: req.user.user._id,
channle: req.params.userId,
});
isFlow = existingFollow ? true : false;
}
const user = await User.findById(req.params.userId);
user.isFlow = isFlow;
res.status(200).json({
...lodash.pick(user, ['_id', 'username', 'image', 'followCount']),
isFlow,
});
} catch (error) {
res.status(400).json(error.message);
}
};

关注列表与粉丝列表
关注列表
router\user.js
定义关注列表路由
javascript
// 关注列表
router.get('/followList/:userId', userConroller.followList);
controller\user.js
实现关注列表逻辑
javascript
// 关注列表
const followList = async (req, res) => {
try {
const userId = req.params.userId;
// 获取关注列表
const follows = await Follow.find({ user: userId }).populate('channle');
// 整理返回数据
const followList = follows.map((follow) => {
return lodash.pick(follow.channle, [
'_id',
'username',
'image',
'followCount',
]);
});
res.status(200).json(followList);
} catch (error) {
res.status(400).json(error.message);
}
};

粉丝列表
router\user.js
定义获取粉丝列表路由
javascript
// 获取粉丝列表
router.get('/fansList', verifyToken(), userConroller.fansList);
controller\user.js
实现获取粉丝列表逻辑
javascript
// 粉丝列表
const fansList = async (req, res) => {
try {
const userId = req.user.user._id;
// 获取粉丝列表
const fans = await Follow.find({ channle: userId }).populate('user');
// 整理返回数据
const fansList = fans.map((fan) => {
return lodash.pick(fan.user, ['_id', 'username', 'image', 'followCount']);
});
res.status(200).json(fansList);
} catch (error) {
res.status(400).json(error.message);
}
};

添加视频评论
router\video.js
添加视频评论路由
javascript
// 视频评论
router.post('/comment/:videoId', verifyToken(), videoController.comment);
model\videocommentModel.js
定义评论 Model
javascript
const mongoose = require('mongoose');
const baseSchema = require('./baseModel');
const videocommentSchema = new mongoose.Schema({
content: {
type: String,
required: true,
},
video: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'Video',
},
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User',
},
...baseSchema,
});
module.exports = videocommentSchema;
model\videoModel.js
视频 Model 新增评论数字段
javascript
// 评论数
commentCount: {
type: Number,
default: 0,
},
controller\videoController.js
处理新增评论
javascript
// 发表视频评论
exports.comment = async (req, res) => {
var { videoId } = req.params;
var { content } = req.body;
var userId = req.user.user._id;
try {
var video = await Video.findById(videoId);
if (!video) {
return res.status(404).send({
code: 404,
message: '视频不存在',
});
}
const videocommentModel = new Videocomment({
content,
video: videoId,
user: userId,
});
var dbBack = await videocommentModel.save();
// 评论数加1
video.commentCount += 1;
await video.save();
res.status(201).send({
code: 201,
message: '评论成功',
data: dbBack,
});
} catch (error) {
console.log('error', error);
res.status(500).send({
code: 500,
message: '评论失败',
data: error,
});
}
};

视频评论列表与删除评论
视频评论列表
router\video.js
定义视频评论列表路由
javascript
// 视频评论列表
router.post(
'/commentlist/:videoId',
verifyToken(false),
videoController.commentlist
);
controller\videoController.js
实现视频评论列表逻辑
javascript
// 获取分页视频评论列表
exports.commentlist = async (req, res) => {
var { videoId } = req.params;
var { page = 1, size = 10 } = req.body;
page = parseInt(page);
size = parseInt(size);
try {
var dbBack = await Videocomment.find({ video: videoId })
.skip((page - 1) * size)
.limit(size)
.sort({
createdAt: -1,
})
.populate('user');
// 评论总条数
var total = await Videocomment.countDocuments({ video: videoId });
res.status(200).send({
code: 200,
message: '视频评论列表获取成功',
data: dbBack,
total: total,
});
} catch (error) {
res.status(500).send({
code: 500,
message: '视频评论列表获取失败',
data: error,
});
}
};

删除评论
router\video.js
定义删除评论路由
javascript
// 删除评论
router.delete(
'/comment/:videoId/:commentId',
verifyToken(),
videoController.deleteComment
);
controller\videoController.js
处理删除视频评论逻辑
javascript
// 删除视频评论
exports.deleteComment = async (req, res) => {
var { videoId, commentId } = req.params;
var userId = req.user.user._id;
try {
var video = await Video.findById(videoId);
if (!video) {
return res.status(404).send({
code: 404,
message: '视频不存在',
});
}
var videocomment = await Videocomment.findById(commentId);
if (!videocomment) {
return res.status(404).send({
code: 404,
message: '评论不存在',
});
}
if (videocomment.user.toString() !== userId.toString()) {
return res.status(403).send({
code: 403,
message: '无权限删除该评论',
});
}
await Videocomment.deleteOne({ _id: commentId });
// 评论数减1
video.commentCount -= 1;
await video.save();
res.status(200).send({
code: 200,
message: '评论删除成功',
});
} catch (error) {
res.status(500).send({
code: 500,
message: '评论删除失败',
data: error,
});
}
};

喜欢与不喜欢视频
点赞视频
router\video.js
javascript
// 点赞视频
router.post('/like/:videoId', verifyToken(), videoController.likeVideo);
model\videolikeModel.js
视频点赞 Model
javascript
const mongoose = require('mongoose');
const baseSchema = require('./baseModel');
const videolikeSchema = new mongoose.Schema({
video: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'Video',
},
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User',
},
like: {
type: Number,
required: true,
enum: [1, -1],
},
...baseSchema,
});
module.exports = videolikeSchema;
model\videoModel.js
新增点赞数和取消点赞数
javascript
// 点赞数
likeCount: {
type: Number,
default: 0,
},
// 踩数
disLikeCount: {
type: Number,
default: 0,
},
controller\videoController.js
处理点赞逻辑
javascript
// 点赞视频
exports.likeVideo = async (req, res) => {
var { videoId } = req.params;
var userId = req.user.user._id;
try {
var video = await Video.findById(videoId);
if (!video) {
return res.status(404).send({
code: 404,
message: '视频不存在',
});
}
// 检查用户是否已经点赞过该视频
let islike = true;
var doc = await Videolike.findOne({ video: videoId, user: userId });
if (doc && doc.like === 1) {
// 用户已经点赞,取消点赞
await doc.remove();
islike = false;
} else if (doc && doc.like === -1) {
// 用户已经点踩,修改为点赞
doc.like = 1;
await doc.save();
} else {
// 用户未点赞,创建新的点赞记录
const videolikeModel = new Videolike({
video: videoId,
user: userId,
like: 1,
});
await videolikeModel.save();
}
// 点赞数和踩数
var likeCount = await Videolike.countDocuments({ video: videoId, like: 1 });
var dislikeCount = await Videolike.countDocuments({
video: videoId,
like: -1,
});
video.likeCount = likeCount;
video.dislikeCount = dislikeCount;
await video.save();
res.status(200).send({
code: 200,
message: '点赞成功',
data: {
...video.toJSON(),
islike: islike,
},
});
} catch (error) {
res.status(500).send({
code: 500,
message: '点赞失败',
data: error,
});
}
};

取消喜欢视频
router\video.js
javascript
// 点踩视频
router.post('/dislike/:videoId', verifyToken(), videoController.dislikeVideo);
controller\videoController.js
javascript
// 点踩视频
exports.dislikeVideo = async (req, res) => {
var { videoId } = req.params;
var userId = req.user.user._id;
try {
var video = await Video.findById(videoId);
if (!video) {
return res.status(404).send({
code: 404,
message: '视频不存在',
});
} // 检查用户是否已经点踩过该视频
let isdislike = true;
var doc = await Videolike.findOne({ video: videoId, user: userId });
if (doc && doc.like === -1) {
// 用户已经点踩,取消点踩
await doc.remove();
} else if (doc && doc.like === 1) {
// 用户已经点赞,修改为点踩
doc.like = -1;
await doc.save();
isdislike = false;
} else {
// 用户未点踩,创建新的点踩记录
const videolikeModel = new Videolike({
video: videoId,
user: userId,
like: -1,
});
await videolikeModel.save();
isdislike = false;
}
// 点赞数和踩数
var likeCount = await Videolike.countDocuments({ video: videoId, like: 1 });
var dislikeCount = await Videolike.countDocuments({
video: videoId,
like: -1,
});
video.likeCount = likeCount;
video.dislikeCount = dislikeCount;
await video.save();
res.status(200).send({
code: 200,
message: '点踩成功',
data: {
...video.toJSON(),
isdislike: isdislike,
},
});
} catch (error) {
res.status(500).send({
code: 500,
message: '点踩失败',
data: error,
});
}
};

喜欢的视频列表及视频详情优化
喜欢的视频列表
router\video.js
新建喜欢的视频列表路由
javascript
// 喜欢的视频列表
router.get('/likelist', verifyToken(), videoController.likelist);
controller\videoController.js
实现获取喜欢的视频列表逻辑
javascript
// 分页获取喜欢的视频列表
exports.likelist = async (req, res) => {
var userId = req.user.user._id;
const { page = 1, pageSize = 10 } = req.query;
try {
var dbBack = await Videolike.find({ user: userId, like: 1 })
.skip((page - 1) * pageSize)
.limit(pageSize)
.populate('video', '_id title vodvideoId user');
// 喜欢的视频总数
var total = await Videolike.countDocuments({ user: userId, like: 1 });
res.status(200).send({
code: 200,
message: '获取喜欢视频列表成功',
data: dbBack,
total,
});
} catch (error) {
res.status(500).send({
code: 500,
message: '获取喜欢视频列表失败',
data: error,
});
}
};

视频详情优化
controller\videoController.js
获取是否喜欢此视频、踩视频、订阅视频作者
javascript
// 获取视频详情
exports.videodetail = async (req, res) => {
var { videoId } = req.params;
try {
var dbBack = await Video.findById(videoId).populate(
'user',
'_id username cover'
);
if (dbBack) {
var videoInfo = dbBack.toJSON();
videoInfo.isLike = false;
videoInfo.isDisLike = false;
videoInfo.isSubscribe = false;
// 是否登录
if (req.user) {
const userId = req.user.user._id;
// 是否喜欢此视频
videoInfo.isLike = (await Videolike.findOne({
video: videoId,
user: userId,
like: 1,
}))
? 1
: -1;
// 是否踩此视频
videoInfo.isDisLike = (await Videolike.findOne({
video: videoId,
user: userId,
like: -1,
}))
? 1
: -1;
// 是否关注视频作者
videoInfo.isSubscribe = (await Follow.findOne({
user: userId,
channle: videoInfo.user._id,
}))
? 1
: -1;
}
res.status(200).send({
code: 200,
message: '视频详情获取成功',
data: videoInfo,
});
} else {
res.status(404).send({
code: 404,
message: '视频不存在',
});
}
} catch (error) {
console.log('error', error);
res.status(500).send({
code: 500,
message: '视频详情获取失败',
data: error,
});
}
};
