第12章 koa框架重构篇 - Koa框架项目重构

Koa项目重构-基础设施搭建

安装项目需要的依赖

javascript 复制代码
{
  "name": "koa-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "nodemon app.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@alicloud/pop-core": "^1.7.12",
    "@koa/cors": "^3.3.0",
    "@koa/router": "^10.1.1",
    "ioredis": "^5.0.6",
    "joi": "^17.6.0",
    "koa": "^2.13.4",
    "koa-body": "^5.0.0",
    "mongoose": "^6.3.6",
    "nodemon": "^2.0.16"
  }
}

index.js

应用入口

javascript 复制代码
const Koa = require('koa');
const cors = require('@koa/cors');
const koaBody = require('koa-body');
const router = require('./router');
const app = new Koa();

// 允许跨域
app.use(cors());
// 解析请求体
app.use(koaBody());

// 注册路由
app.use(router.routes());
app.use(router.allowedMethods());

// 监听错误
app.on('error', (err, ctx) => {
  console.log(err);
});

app.listen(3000, () => {
  console.log('server is running at http://localhost:3000');
});

router\index.js

路由

javascript 复制代码
const Router = require('koa-router');
const userController = require('../controller/userController');
const router = new Router();

router.get('/', userController.index);

module.exports = router;

controller\userController.js

控制器

javascript 复制代码
module.exports.index = async (ctx) => {
  ctx.body = 'Hello World';
};
数据库链接模型创建

model\userModel.js

用户模型

javascript 复制代码
const mongoose = require('mongoose')
const md5 = require('../util/md5')

const baseModel = require('./baseModel')

const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true
  },
  email: {
    type: String,
    required: true
  },
  password: {
    type: String,
    required: true,
    set: value => md5(value),
    select:false
  },
  phone: {
    type: String,
    required: true
  },
  image: {
    type: String,
    default: null
  },
  cover:{
    type: String,
    default: null
  },
  channeldes:{
    type: String,
    default: null
  },
  subscribeCount:{
    type:Number,
    default:0
  },
  ...baseModel
})

module.exports = userSchema

model\index.js

javascript 复制代码
const mongoose = require('mongoose')
const { mongoPath } = require('../config/config.default')
async function mian(){
  await mongoose.connect(mongoPath)
}
mian()
.then(res=>{
  console.log('mongoDB 连接成功');
})
.catch(err=>{
  console.log(err);
})

module.exports = {
  User:mongoose.model('User',require('./userModel')),
  Video:mongoose.model('Video',require('./videoModel')),
  Subscribe:mongoose.model('Subscribe',require('./subscribeModel')),
  Videocomment:mongoose.model('Videocomment',require('./videocommentModel')),
  Videolike:mongoose.model('Videolike',require('./videolikeModel')),
  collectModel:mongoose.model('CollectModel',require('./collectModel'))
}
用户登录与数据验证

安装 Joi 请求数据校验库

javascript 复制代码
// ^17.6.0
pnpm i joi

https://joi.dev/api/?v=17.13.3

router\index.js

新建注册路由

javascript 复制代码
// 注册
router.post('/user/register', registerValidate, userController.register);

middleware\userValidate.js

注册路由参数校验中间件

javascript 复制代码
const Joi = require('joi');
const { User } = require('../model');
// 用户注册参数验证
module.exports.registerValidate = async (ctx, next) => {
  const schema = Joi.object({
    username: Joi.string().required(),
    password: Joi.string().min(6).required(),
    email: Joi.string().email().required(),
    phone: Joi.string().min(11).max(11).required(),
  }).validate(ctx.request.body);

  // 验证失败
  if (schema.error) {
    ctx.body = {
      code: 400,
      message: schema.error.message,
    };
    return;
  }
  // 邮箱是否注册
  const user = await User.findOne({
    email: ctx.request.body.email,
  });
  if (user) {
    ctx.body = {
      code: 400,
      message: '邮箱已注册',
    };
    return;
  }
  await next();
};

controller\userController.js

实现用户注册逻辑

javascript 复制代码
const { User } = require('../model');
// 用户注册
module.exports.register = async (ctx) => {
  const userModal = new User(ctx.request.body);
  const dbBack = await userModal.save();
  ctx.body = {
    code: 200,
    message: '注册成功',
    data: dbBack,
  };
};
用户登录与Token验证

安装 jsonwebtoken 库

javascript 复制代码
// jsonwebtoken ^9.0.3
pnpm i jsonwebtoken 

util\jwt.js

定义生成token和校验token 工具函数。

javascript 复制代码
const jwt = require('jsonwebtoken')
const { promisify } = require('util')
const tojwt = promisify(jwt.sign)
const verify = promisify(jwt.verify)

// 验证token 
module.exports.verifyToken = function (required = true) {
  return async (ctx, next) => {
    var token = ctx.headers.authorization
    token = token ? token.split("Bearer ")[1] : null
    if(token){
      try {
        var userInfo =  await verify(token,'koa-viode')
        ctx.user = userInfo
        await next()
      } catch (error) {
        ctx.throw(402,error)
      }
    }else if(required){
      ctx.throw(402,'无效的token')
    }else{
      await next()
    }
  }
}

// 生成token
module.exports.createToken = async userInfo => {
  var token = await tojwt({ userInfo }, 'koa-viode', {
    expiresIn: 60 * 60 * 24
  })
  return token
}

router\index.js

创建用户登录路由

javascript 复制代码
// 登录
router.post('/user/login', userController.login);

middleware\userValidate.js

自定义用户登录参数校验中间件

javascript 复制代码
// 用户登录参数校验
module.exports.loginValidate = async (ctx, next) => {
  const schema = Joi.object({
    password: Joi.string().min(6).required(),
    email: Joi.string().email().required(),
  }).validate(ctx.request.body);

  // 验证失败
  if (schema.error) {
    ctx.body = {
      code: 400,
      message: schema.error.message,
    };
    return;
  }
  // 邮箱是否注册
  const user = await User.findOne({
    email: ctx.request.body.email,
  });
  if (!user) {
    ctx.body = {
      code: 400,
      message: '邮箱未注册',
    };
    return;
  }
  await next();
};

controller\userController.js

实现用户登录逻辑

javascript 复制代码
// 用户登录
module.exports.login = async (ctx) => {
  const { email, password } = ctx.request.body;
  const user = await User.findOne({
    email,
    password,
  });
  if (!user) {
    ctx.body = {
      code: 400,
      message: '用户名或密码错误',
    };
    return;
  }
  // 生成 token
  const token = await createToken(user._doc);
  user._doc.token = token;
  ctx.body = {
    code: 200,
    message: '登录成功',
    data: user._doc,
  };
};
用户频道模块-获取频道详情

router\index.js

创建获取频道详情路由

javascript 复制代码
// 获取频道信息
router.get('/user/getuser/:userid', verifyToken(true), userController.getuser);

controller\userController.js

实现获取频道详情逻辑

javascript 复制代码
// 获取频道信息
module.exports.getuser = async (ctx) => {
  const userid = ctx.request.params.userid;
  console.log('ctx.user', ctx.user);
  const loginUserId = ctx.user ? ctx.user.userInfo._id : null;
  let isSubscribe = false;
  if (loginUserId) {
    // 是否订阅此频道
    const subscribe = await Subscribe.findOne({
      user: loginUserId,
      channel: userid,
    });
    if (subscribe) {
      isSubscribe = true;
    }
  }
  // 获取 userid 频道信息
  const userInfoDb = await User.findOne(
    {
      _id: userid,
    },
    ['username', 'image', 'cover', 'channeldes']
  );
  const userinfo = userInfoDb._doc;
  userinfo.isSubscribe = isSubscribe;
  ctx.body = {
    code: 200,
    message: '获取频道信息成功',
    data: userinfo,
  };
};
用户频道模块-关注频道

router\index.js

创建关注频道路由

javascript 复制代码
// 关注频道
router.get(
  '/user/subscribe/:subscribeid',
  verifyToken(true),
  userController.subscribe
);

controller\userController.js

实现关注频道逻辑

javascript 复制代码
module.exports.subscribe = async (ctx) => {
  const subscribeid = ctx.request.params.subscribeid;
  const userid = ctx.user.userInfo._id;
  // 是否关注自己
  if (subscribeid === userid) {
    ctx.body = {
      code: 400,
      message: '不能关注自己',
    };
    return;
  }
  const subscribe = await Subscribe.findOne({
    user: userid,
    channel: subscribeid,
  });
  // 是否已关注
  if (subscribe) {
    ctx.body = {
      code: 400,
      message: '已关注',
    };
    return;
  }
  // 关注频道
  const subscribeModal = new Subscribe({
    user: userid,
    channel: subscribeid,
  });
  const dbBack = await subscribeModal.save();
  if (dbBack) {
    // 粉丝数+1
    const subcribeUser = await User.findById(subscribeid, [
      'username',
      'image',
      'cover',
      'channeldes',
    ]);
    subcribeUser.followCount += 1;
    await subcribeUser.save();
    ctx.body = {
      code: 200,
      message: '关注成功',
      data: subcribeUser,
    };
  } else {
    ctx.body = {
      code: 400,
      message: '关注失败',
    };
  }
};
我的关注列表

router\index.js

创建关注频道列表路由

javascript 复制代码
// 关注列表
router.get(
  '/user/subscribe/subscribelist',
  verifyToken(false),
  userController.subscribeList
);

controller\userController.js

实现获取频道列表

javascript 复制代码
// 关注频道列表
module.exports.subscribeList = async (ctx) => {
  const userid = ctx.user.userInfo._id;
  const subscribeList = await Subscribe.find({
    user: userid,
  }).populate('channel', [
    'username',
    'image',
    'cover',
    'channeldes',
    'followCount',
  ]);
  console.log('subscribeList', subscribeList);
  ctx.body = {
    code: 200,
    message: '获取关注频道列表成功',
    data: subscribeList,
  };
};
视频模块-获取视频凭证与客户端视频上传

安装阿里云视频上传库

javascript 复制代码
pnpm i @alicloud/pop-core

router\index.js

创建获取视频上传凭证路由

javascript 复制代码
// 视频模块 - 获取视频上传凭证
router.get('/video/getvod', verifyToken(true), vodController.getVod);

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\index.js

创建创建视频路由

javascript 复制代码
// 视频模块 - 创建视频
router.post(
  '/video/createvideo',
  verifyToken(true),
  videoController.createVideo
);

controller\videoController.js

实现创建视频信息入库

javascript 复制代码
// 上传视频
exports.createVideo = async (ctx) => {
  var body = ctx.request.body;
  body.user = ctx.user.userInfo._id;

  const videoModel = new Video(body);
  try {
    var dbBack = await videoModel.save();
    ctx.body = {
      code: 201,
      message: '视频上传成功',
      data: dbBack,
    };
  } catch (error) {
    ctx.body = {
      code: 500,
      message: '视频上传失败',
      data: error,
    };
  }
};
视频模块-获取播放地址

router\index.js

创建获取视频播放地址路由

javascript 复制代码
// 视频模块 - 获取视频播放地址
router.get(
  '/video/getPlay/:videoId',
  verifyToken(true),
  vodController.getPlayUrl
);

controller\vodController.js

实现获取视频播放地址

javascript 复制代码
// 获取视频播放地址
exports.getPlayUrl = async (ctx) => {
  var client = initVodClient('LTAI5t3Yxxxxxx', 'xxxxxxxxxxxxxxxxxxxxxx');
  const { videoId } = ctx.request.params;
  const playUrlBack = await client.request('GetPlayInfo', {
    VideoId: videoId,
  });
  ctx.body = playUrlBack;
};
视频模块-获取用户视频列表

router\index.js

创建获取用户发布的视频列表路由

javascript 复制代码
// 视频模块 - 获取用户发布的视频列表
router.get(
  '/video/videolist/:userid',
  verifyToken(false),
  videoController.videolist
);

controller\videoController.js

实现获取用户发布的视频列表

javascript 复制代码
// 获取指定用户发布的视频分页列表
exports.videolist = async (ctx) => {
  const userid = ctx.request.params.userid;
  var { page = 1, size = 10 } = ctx.request.query;
  page = parseInt(page);
  size = parseInt(size);
  try {
    var dbBack = await Video.find({ user: userid })
      .skip((page - 1) * size)
      .limit(size)
      .sort({
        createdAt: -1,
      })
      .populate('user', [
        'username',
        'image',
        'cover',
        'channeldes',
        'followCount',
      ]);
    // 视频总条数
    var total = await Video.countDocuments({ user: userid });
    ctx.body = {
      code: 200,
      message: '视频列表获取成功',
      total: total,
      data: dbBack,
    };
  } catch (error) {
    ctx.body = {
      code: 500,
      message: '视频列表获取失败',
      data: error,
    };
  }
};
视频模块-获取视频详情

router\index.js

创建获取视频详情路由

javascript 复制代码
// 视频模块 - 视频详情
router.get('/video/:videoId', verifyToken(true), videoController.videodetail);

controller\videoController.js

获取视频详情

javascript 复制代码
// 获取视频详情
exports.videodetail = async (ctx) => {
  var { videoId } = ctx.request.params;
  try {
    var dbBack = await Video.findById(videoId).populate('user', [
      'username',
      'image',
      'cover',
      'channeldes',
      'followCount',
    ]);
    const videoinfo = dbBack._doc;
    if (videoinfo) {
      const { getVodPlay } = require('./vodController');
      const vodInfo = await getVodPlay(videoinfo.vodvideoId);
      videoInfo.vod = vodInfo;
      videoInfo.isLike = false;
      videoInfo.isDisLike = false;
      videoInfo.isSubscribe = false;

      // 是否登录
      if (ctx.user) {
        const userId = ctx.user.userInfo._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;
      }
      // 热度加 1
      // hotInc(videoId, 1);
      ctx.body = {
        code: 200,
        message: '视频详情获取成功',
        data: videoInfo,
      };
    } else {
      ctx.body = {
        code: 404,
        message: '视频不存在',
      };
    }
  } catch (error) {
    console.log('error', error);
    ctx.body = {
      code: 500,
      message: '视频详情获取失败',
      data: error,
    };
  }
};

controller\vodController.js

封装获取视频播放详情

javascript 复制代码
const getVodPlay = async (vodid) => {
  var client = initVodClient('LTAI5t3Yxxxxxx', 'xxxxxxxxxxxxxxxxxxxxxx');
  const playUrlBack = await client.request('GetPlayInfo', {
    VideoId: vodid,
  });
  return playUrlBack;
};

// 获取视频播放地址
exports.getVodDetail = async (ctx) => {
  var { vodid } = ctx.request.params;
  const playUrlBack = await getVodPlay(vodid);
  res.status(200).json({ playUrl: playUrlBack });
};

module.exports.getVodPlay = getVodPlay;
交互模块-视频评论

router\index.js

创建发布视频评论路由

javascript 复制代码
// 视频模块 - 发表视频评论
router.post(
  '/video/comment/:videoId',
  verifyToken(true),
  videoController.createComment
);

controller\videoController.js

实现发布视频评论

javascript 复制代码
// 发表视频评论
exports.createComment = async (ctx) => {
  var { videoId } = ctx.request.params;
  var { content } = ctx.request.body;
  var userId = ctx.user.userInfo._id;
  try {
    var video = await Video.findById(videoId);
    if (video) {
      const videocommentModel = new Videocomment({
        content,
        video: videoId,
        user: userId,
      });
      var dbBack = await videocommentModel.save();
      // 评论数加1
      video.commentCount += 1;
      await video.save();
      // 热度加 2
      // hotInc(videoId, 2);
      ctx.body = {
        code: 201,
        message: '评论成功',
        data: dbBack,
      };
    } else {
      ctx.body = {
        code: 404,
        message: '视频不存在',
      };
    }
  } catch (error) {
    console.log('error', error);
    ctx.body = {
      code: 500,
      message: '评论失败',
      data: error,
    };
  }
};
相关推荐
cike_y2 小时前
JavaBean&MVC三层架构
java·架构·mvc·javaweb·java开发
鸡吃丸子2 小时前
React Native入门详解
开发语言·前端·javascript·react native·react.js
漂亮的小碎步丶2 小时前
【启】Java中高级开发51天闭关冲刺计划(聚焦运营商/ToB领域)
java·开发语言
qq_428723242 小时前
英语歌10个月之前启蒙磨耳朵
前端
Hao_Harrision2 小时前
50天50个小项目 (React19 + Tailwindcss V4) ✨ | DrinkWater(喝水记录组件)
前端·react.js·typescript·vite7·tailwildcss
SadSunset2 小时前
(19)Bean的循环依赖问题
java·开发语言·前端
⑩-2 小时前
Java自定义业务异常类
java
Adellle2 小时前
Java爬虫入门(2/5)
java·爬虫
JIngJaneIL2 小时前
基于Java+ vue图书管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端