Skip to content

nodejs coderhub

前言:项目介绍

Coderhub旨在创建一个程序员分享生活动态的平台。

完整的项目接口包括:

  • 面向用户的业务接口;
  • 面向企业或者内部的后台管理接口;

完成的功能如下:

  • 1.用户管理系统
  • 2.内容管理系统
  • 3.内容评论管理
  • 4.内容标签管理
  • 5.文件管理系统
  • ...其他功能其实都是非常相似的

第一部分

第一节:项目结构搭建

  • 创建项目文件夹    coderhub
  • npm init -y    生成package.json文件
  • npm install koa    安装koa框架

目录结构的划分:

  • 按照功能模块划分;
  • 按照业务模块划分;

我们按照功能模块来划分目录结构:参考egg.js框架的目录结构

  • 根目录创建src文件夹
  • src文件夹下创建index.js或者main.js(我们采用main.js)
  • src文件夹下创建app文件夹,app文件夹下新建index.js文件
  • src文件夹下创建controller、router、service、utils文件夹,分别存放控制器、路由、服务器、工具等文件
  • main.js引入路由,创建app路由对象,启动监听,代码如下
javascript
const Koa = require('koa')

const app = new Koa()

app.listen(8000,()=>{
  console.log('服务器启动成功~');
})
  • 安装nodemon在开发时依赖:npm i nodemon -D,并且在package.json中配置启动脚本

第二节:配置信息写入到环境变量

  • 把main.js中的代码抽取到app/index.js
javascript
/*------------------------------app/index.js---------------------------------*/
const Koa = require('koa')

const app = new Koa()

module.exports = app



/*------------------------------main.js---------------------------------*/
const app = require('./app')

app.listen(8000,()=>{
  console.log('服务器启动成功~');
})
  • 根目录创建.env文件用于存放全局环境变量,并且新建APP_PROT环境变量存储服务器端口号
javascript
APP_PORT=8000
  • 安装解析环境变量的库:npm i dotenv
  • app目录下创建config.js,编写以下代码
javascript
const dotenv = require('dotenv')

dotenv.config()

module.exports = {
	APP_PORT
} = process.env
  • main.js引入config,应用环境变量
javascript
const app = require('./app')

const config = require('./app/config')

app.listen(config.APP_PORT,()=>{
  console.log(`服务器在${config.APP_PORT}端口启动成功~`);
})

第三节:用户注册接口搭建

  • 安装koa路由模块:npm i koa-router
  • 在app/index.js中导入路由,创建一个用户路由
javascript
const Koa = require('koa')

const Router = require('koa-router')

const app = new Koa()

const userRouter = new Router({prefix: '/users'});
userRouter.post('/',(ctx,next)=>{
  ctx.body = "创建用户成功"
})

app.use(userRouter.routes())
app.use(userRouter.allowedMethods())

module.exports = app

第四节:用户注册接口的结构分层

  • 在router目录下创建user.router.js,把app/index.js中的路由代码抽取过来
javascript
/*------------------------------router/user.router.js---------------------------------*/
const Router = require('koa-router')

const userRouter = new Router({prefix: '/users'});
userRouter.post('/',(ctx,next)=>{
  ctx.body = "创建用户成功"
})

module.exports = userRouter


/*------------------------------app/index.js---------------------------------*/
const Koa = require('koa')

const userRouter = require('../router/user.router')

const app = new Koa()

app.use(userRouter.routes())
app.use(userRouter.allowedMethods())

module.exports = app
  • 把路由的业务处理代码(函数)抽取到controller中
    • 在controller文件夹下新建user.controller.js
    • 以类的形式定义,返回类的实例(对象),把处理业务定义为类的原型属性,导入时可以直接获取对象的属性
javascript
/*-------------------------controcller/user.controller.js---------------------------*/
class UserController {
  async create(ctx, next) {
    // 获取用户请求传递的参数

    // 查询数据

    // 返回数据
  }
}

module.exports = new UserController()


/*------------------------------router/user.router.js---------------------------------*/
const Router = require('koa-router')

const { create } = require('../controller/user.controller')

const userRouter = new Router({ prefix: '/users' });

userRouter.post('/', create)

module.exports = userRouter
  • 在service目录下创建user.service.js,与数据库交互的业务全部放到service目录下
javascript
/*------------------------------service/user.service.js---------------------------------*/
class UserService {
  async create(user){
    // 将user存储到数据库中
    return "创建用户成功";
  }
}

module.exports = new UserService();


/*-------------------------controcller/user.controller.js---------------------------*/
const service = require('../service/user.service')

class UserController {
  async create(ctx, next) {
    // 获取用户请求传递的参数

    // 查询数据
    const result = await service.create()

    // 返回数据
    ctx.body = result
  }
}

module.exports = new UserController()
  • 安装解析json的模块:npm i koa-bodyparser,在app/index.js使用它
javascript
/*------------------------------app/index.js---------------------------------*/
const Koa = require('koa')
const bodyparser = require('koa-bodyparser')

const userRouter = require('../router/user.router')

const app = new Koa()


app.use(bodyparser())
app.use(userRouter.routes())
app.use(userRouter.allowedMethods())

module.exports = app
  • 在controller/user.controller.js中获取请求参数(json)
javascript
/*-----------------------controller/user.controller.js-----------------------*/
const service = require('../service/user.service')

class UserController {
  async create(ctx, next) {
    // 获取用户请求传递的参数
    const user = ctx.request.body

    // 查询数据
    const result = await service.create(user)

    // 返回数据
    ctx.body = result
  }
}

module.exports = new UserController()

/*-----------------------service/user.service.js-----------------------*/
class UserService {
  async create(user){
    console.log("将用户数据保存到数据库中:",user);
    // 将user存储到数据库中
    return "创建用户成功";
  }
}

module.exports = new UserService();

第五节:用户注册接口的数据库保存

  • 在mysql中创建usesr表(用户表)
sql
CREATE TABLE IF NOT EXISTS `users` (
	id INT PRIMARY KEY AUTO_INCREMENT,
	name VARCHAR(30) NOT NULL UNIQUE,
	password VARCHAR(50) NOT NULL,
	createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
	updateAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP 
);
  • 安装mysql2操作数据库:npm i mysql2
  • 在app目录下创建database.js文件,在.env文件中新建环境变量存储数据库信息,并且在app/config.js文件中导出
javascript
/*-----------------------.env-----------------------*/
APP_PORT=8000
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_DATABASE=coderhub
MYSQL_USER=root
MYSQL_PASSWORD=54088qq


/*-----------------------app/config.js-----------------------*/
const dotenv = require('dotenv')

dotenv.config()

module.exports = {
	APP_PORT,
	MYSQL_HOST,
	MYSQL_PORT,
	MYSQL_DATABASE,
	MYSQL_USER,
	MYSQL_PASSWORD
} = process.env


/*-----------------------app/database.js-----------------------*/
const mysql = require('mysql2')

const config = require('./config')

const connections = mysql.createPool({
  host: config.MYSQL_HOST,
  port: config.MYSQL_PORT,
  database: config.MYSQL_DATABASE,
  user: config.MYSQL_USER,
  password: config.MYSQL_PASSWORD
})

connections.getConnection((err, conn) => {
  conn.connect(err => {
    if(err){
      console.log("连接失败~", err);
    }else{
      console.log("数据库连接成功~");
    }
  })
})

module.exports = connections.promise()
  • main.js测试数据库是否连接成功
javascript
const app = require('./app')

const config = require('./app/config')

require('./app/database')

app.listen(config.APP_PORT,()=>{
  console.log(`服务器在${config.APP_PORT}端口启动成功~`);
})
  • 在service/user.service.js中编写sql语句,执行sql语句把前端传递的用户数据插入到数据库
javascript
const connections = require('../app/database')

class UserService {
  async create(user){

    const {name, password} = user;
    const statement = `INSERT INTO users (name, password) VALUES (?, ?);`;

    const result = await connections.execute(statement,[name, password]);
    
    // 将user存储到数据库中
    return result;
  }
}

module.exports = new UserService();

第六节:用户注册接口-验证用户

  • 创建验证用户信息中间件
    • 根目录创建一个middleware文件夹,在里面新建一个user.middleware.js文件
      • 获取用户名和密码
      • 判断用户名或者密码不能空,如果为空抛出错误
      • 判断这次注册的用户名是没有被注册过的,如果用户存在抛出错误
    • 在app目录下创建一个error.handle专门处理错误的文件
    • src目录下创建一个constants文件夹,里面新建一个error-types.js保存一些错误信息的常量
    • app/index.js监听错误
    • router/user.router.js使用验证用户信息中间件
    • service/user.service.js处理sql请求
javascript
/*--------------------------middleware/user.middleware.js--------------------------*/
const errorTypes = require('../constants/error-types')

const service = require('../service/user.service')

const verifyUser = async (ctx, next) => {
  // 1.获取用户名和密码
  const { name, password } = ctx.request.body;

  // 2.判断用户名或者密码不能空
  if (!name || !password) {
    const error = new Error(errorTypes.NAME_OR_PASSWORD_IS_REQUIRED)
    return ctx.app.emit('error', error, ctx)
  }

  // 3.判断这次注册的用户名是没有被注册过的
  const result = await service.getUserByName(name)
  if (result.length) {
    const error = new Error(errorTypes.USER_ALREADY_EXISTS)
    return ctx.app.emit('error', error, ctx)
  }
  await next()
}

module.exports = {
  verifyUser
}
javascript
/*------------------------------constants/error-types.js--------------------------*/
const NAME_OR_PASSWORD_IS_REQUIRED = "name_or_password_is_require";
const USER_ALREADY_EXISTS = "user_already_exists";

module.exports = {
  NAME_OR_PASSWORD_IS_REQUIRED,
  SER_ALREADY_EXISTS
}
javascript
/*-----------------------app/error.handle.js--------------------------*/
const errorTypes = require('../constants/error-types')

const errorHandle = (error, ctx) => {
  let status, message;
  switch (error.message) {
    case errorTypes.NAME_OR_PASSWORD_IS_REQUIRED:
      status = 400 // Bad Request
      message = "用户名或者密码不能为空~"
      break
    case errorTypes.USER_ALREADY_EXISTS:
      status = 409 // Conflict
      message = "用户名已存在~"
      break
    default:
      status = 404
      message = "NOT FOUND"
  }
  ctx.status = 404
  ctx.body = message
}

module.exports = errorHandle
javascript
/*----------------------------app/index.js----------------------------*/
const Koa = require('koa')
const bodyparser = require('koa-bodyparser')

const userRouter = require('../router/user.router')

const errorHandle = require('./error.handle')

const app = new Koa()


app.use(bodyparser())
app.use(userRouter.routes())
app.use(userRouter.allowedMethods())

app.on('error',errorHandle)

module.exports = app
javascript
/*-----------------------router/user.router.js--------------------------*/
const Router = require('koa-router')

const { create } = require('../controller/user.controller')

const { verifyUser } = require('../middleware/user.middleware')

const userRouter = new Router({ prefix: '/users' });

userRouter.post('/', verifyUser, create)

module.exports = userRouter
javascript
/*-----------------------service/user.service.js--------------------------*/
const connections = require('../app/database')

class UserService {
  async create(user) {

    const { name, password } = user;
    const statement = `INSERT INTO users (name, password) VALUES (?, ?);`;

    const result = await connections.execute(statement, [name, password]);

    // 将user存储到数据库中
    return result[0];
  }

  async getUserByName(name) {
    const statement = `SELECT * FROM users WHERE name=?;`;
    const result = await connections.execute(statement, [name])
    return result[0]
  }
}

module.exports = new UserService();

第二部分

第一节:用户注册接口-对密码进行加密

  • 添加加密用户密码中间件
javascript
/*----------------------router/user.router.js-----------------*/
const Router = require('koa-router')

const { create } = require('../controller/user.controller')

const { verifyUser, handlePassword } = require('../middleware/user.middleware')

const userRouter = new Router({ prefix: '/users' });

userRouter.post('/', verifyUser, handlePassword, create)

module.exports = userRouter
  • 在utils中新建password-handle.js文件,导出加密密码的方法
javascript
/*----------------------utils/password-handle.js-----------------*/
const crypto = require('crypto')

const md5password = (password) => {
  const md5 = crypto.createHash('md5')
  const result = md5.update(password).digest('hex') // 拿到16进制的结果
  return result
}

module.exports = md5password
  • 在middleware/user.middleware.js中创建handlePassword中间件并导入使用md5password这个方法
javascript
/*--------------------middleware/user.middleware.js------------------------*/
const connection = require('../app/database')

const errorTypes = require('../constants/error.types')
const service = require('../service/user.servics')
const md5password = require('../utils/password-handle')

const verifyUser = async (ctx, next) => {
  // 1.获取用户名和密码
  const { name, password } = ctx.request.body;

  // 2.判断用户名或者密码不能空
  if (!name || !password) {
    const error = new Error(errorTypes.NAME_OR_PASSWORD_IS_REQUIRED);
    return ctx.app.emit('error', error, ctx);
  }

  // 3.判断这次注册的用户名是没有被注册过
  const result = await service.getUserByName(name)
  if (result.length) {
    console.log('用户名已存在');
    const error = new Error(errorTypes.USER_ALREADY_EXISTS);
    return ctx.app.emit('error', error, ctx);
  }
  await next()
}

const handlePassword = async (ctx, next) => {
  let { password } = ctx.request.body
  ctx.request.body.password = md5password(password)
  await next()
}

module.exports = {
  verifyUser,
  handlePassword
}

第二节:用户登录接口-登录逻辑结构的搭建

  • 在router文件夹中新建一个auth.router.js文件,auth是授权的意思,表示对登录进行授权,创建并导出这个路由
javascript
/*-------------------router/auth.router.js-----------------------*/
const Router = require('koa-router')

const authRouter = new Router()

const {login} = require('../controller/auth.controller.js')

authRouter.post('/login',login)

module.exports = authRouter
  • 在app/index.js中导入路由
javascript
/*-------------------app/index.js-----------------------*/
const Koa = require('koa')
const bodyParser = require('koa-bodyparser')

const userRouter = require('../router/user.router')
const authRouter = require('../router/auth.router')
const errorHandler = require('./error.handle')

const app = new Koa()

app.use(bodyParser())
app.use(userRouter.routes())
app.use(userRouter.allowedMethods())
app.use(authRouter.routes())
app.use(authRouter.allowedMethods())

app.on('error', errorHandler)

module.exports = app
  • 新建控制器
javascript
/*-------------------controller/auth.controller.js-----------------------*/
class AuthController {
  async login(ctx,next){
      const {name,password} = ctx.request.body
      ctx.body = `登录成功欢迎${name}回来`;
  }
}

module.exports = new AuthController()

第三节:用户登录接口-登录验证逻辑完成

  • 在middleware文件夹下创建auth.middleware.js,校验的中间件文件,应用到路由中
javascript
/*-----------------------middleware/auth.middleware.js--------------*/
const service = require("../service/user.servics")
const errorTypes = require('../constants/error.types')
const md5password = require('../utils/password-handle')

const verifyLogin = async (ctx, next) => {
  // 1.获取用户名和密码
  const { name, password } = ctx.request.body;

  // 2.判断用户名和密码是否为空
  if (!name || !password) {
    const error = new Error(errorTypes.NAME_OR_PASSWORD_IS_REQUIRED);
    return ctx.app.emit('error', error, ctx);
  }

  // 3.判断用户是否存在的
  const result = await service.getUserByName(name)
  const user = result[0]
  if (!user) {
    const error = new Error(errorTypes.USER_DOES_NOT_EXISTS);
    return ctx.app.emit('error', error, ctx);
  }

  // 4.判断密码是否和数据库中的密码是一致(加密)
  if(md5password(password) !== user.password) {
    const error = new Error(errorTypes.PASSWORD_IS_INCORRENT);
    return ctx.app.emit('error', error, ctx);
  }
  
  ctx.user = user
  await next()
}
module.exports = { verifyLogin }
  • 在router/auth.router.js中导入它
javascript
/*-----------------------------router/auth.router.js---------------------*/
const Router = require('koa-router')

const authRouter = new Router()

const { login } = require('../controller/auth.controller.js')

const { verifyLogin } = require('../middleware/auth.middleware')

authRouter.post('/login', verifyLogin, login)

module.exports = authRouter
  • 在constants/error.types.js中创建两个常量(用户不存在/用户密码错误)并导出
javascript
const NAME_OR_PASSWORD_IS_REQUIRED = 'name_or_password_is_required';
const USER_ALREADY_EXISTS = 'user_already_exists';
const USER_DOES_NOT_EXISTS = 'user_does_not_exists';
const PASSWORD_IS_INCORRENT = 'password_is_incorrent';

module.exports = {
  NAME_OR_PASSWORD_IS_REQUIRED,
  USER_ALREADY_EXISTS,
  USER_DOES_NOT_EXISTS,
  PASSWORD_IS_INCORRENT
}
  • 在app/error.handle.js中编写错误处理代码
javascript
const errorTypes = require('../constants/error.types')

const errorHandler = (error, ctx) => {
  let status, message;
  switch (error.message) {
    case errorTypes.NAME_OR_PASSWORD_IS_REQUIRED:
      status = 400; // Bad Request
      message = "用户名或者密码不能为空~"
      break;
    case errorTypes.USER_ALREADY_EXISTS:
      status = 409; // conflict 
      message = "用户名已经存在~"
      break;
    case errorTypes.USER_DOES_NOT_EXISTS:
      status = 400; // 参数错误
      message = "用户名不存在~"
      break;
    case errorTypes.PASSWORD_IS_INCORRENT:
      status = 400; // 参数错误
      message = "密码是错误的~"
      break;
    default:
      status = 404;
      message = "NOT FOUND";
  }

  ctx.status = status;
  ctx.body = message;
}

module.exports = errorHandler

第四节:动态加载所有的路由

  • 在router文件夹中新建index.js作为路由入口文件
javascript
/*-------------------------routes/index.js*/
const fs = require('fs')

const useRoutes = function () {
  fs.readdirSync(__dirname).forEach(file => {
    if (file === 'index.js') return;
    const router = require(`./${file}`)
    this.use(router.routes())
    this.use(router.allowedMethods())
  })
}
module.exports = useRoutes
  • app/index.js就可以不用一个个引入路由了,也不用每个都使用use两次了
javascript
/*--------------------app/index.js------------------------*/
const Koa = require('koa')
const bodyParser = require('koa-bodyparser')

const errorHandler = require('./error.handle')
const useRoutes = require('../router')

const app = new Koa()

app.useRoutes = useRoutes

app.use(bodyParser())
app.useRoutes()

app.on('error', errorHandler)

module.exports = app

第五节:登录接口-jwt颁发令牌

  • npm i jsonwebtoken
  • 在app文件夹中创建keys文件夹,并在终端中生成私钥和公钥保存到keys文件夹中
shell
openssl
> genrsa -out private.key 1024
> rsa -in private.key -pubout -out public.key
  • 在app/config.js文件中读取并导出私钥和公钥
javascript
/*------------------app/config.js-------------------*/
const fs = require("fs")
const path = require('path')
const dotenv = require('dotenv');

const PRIVATE_KEY = fs.readFileSync(path.resolve(__dirname,'keys/private.key'))
const PUBLIC_KEY = fs.readFileSync(path.resolve(__dirname,'keys/public.key'))

dotenv.config()

module.exports = {
  APP_PORT,
  MYSQL_HOST,
  MYSQL_PORT,
  MYSQL_DATABASE,
  MYSQL_USER,
  MYSQL_PASSWORD
} = process.env

module.exports.PRIVATE_KEY = PRIVATE_KEY
module.exports.PUBLIC_KEY = PUBLIC_KEY
  • 在controller/auth.controller.js文件中给用户颁发令牌token
javascript
/*----------------controller/auth.controller.js------------*/
const jwt = require("jsonwebtoken")

const { PRIVATE_KEY, PUBLIC_KEY } = require('../app/config')

class AuthController {
  async login(ctx, next) {
    const { id, name } = ctx.user

    // 颁发令牌
    const token = jwt.sign({ id, name }, PRIVATE_KEY, {
      expiresIn: 60 * 60 * 24,
      algorithm: 'RS256'
    })

    ctx.body = { id, name, token };
  }
}

module.exports = new AuthController()

第六节:登录接口-jwt验证令牌

  • 在router/auth.router.js里创建测试路由验证签名
javascript
/*---------------------router/auth.router.js----------------------*/
const Router = require('koa-router')

const authRouter = new Router()

const { login,success } = require('../controller/auth.controller.js')

const { verifyLogin,verifyAuth } = require('../middleware/auth.middleware')

authRouter.post('/login', verifyLogin, login)
authRouter.get('/test', verifyAuth, success)

module.exports = authRouter
  • 在middleware/auth.middleware.js创建verifyAuth验证签名的函数
javascript
/*------------------------middleware/auth.middleware.js------------------------*/
const jwt = require('jsonwebtoken')
const fs = require('fs')
const path = require('path')

const service = require("../service/user.servics")
const errorTypes = require('../constants/error.types')
const md5password = require('../utils/password-handle')

const {PUBLIC_KEY} = require('../app/config')

const verifyLogin = async (ctx, next) => {
  // 1.获取用户名和密码
  const { name, password } = ctx.request.body;

  // 2.判断用户名和密码是否为空
  if (!name || !password) {
    const error = new Error(errorTypes.NAME_OR_PASSWORD_IS_REQUIRED);
    return ctx.app.emit('error', error, ctx);
  }

  // 3.判断用户是否存在的
  const result = await service.getUserByName(name)
  const user = result[0]
  if (!user) {
    const error = new Error(errorTypes.USER_DOES_NOT_EXISTS);
    return ctx.app.emit('error', error, ctx);
  }

  // 4.判断密码是否和数据库中的密码是一致(加密)
  if (md5password(password) !== user.password) {
    const error = new Error(errorTypes.PASSWORD_IS_INCORRENT);
    return ctx.app.emit('error', error, ctx);
  }
  ctx.user = user
  await next()
}

const verifyAuth = async (ctx, next) => {
  console.log("验证授权的middleware");
  // 1.获取token
  const authorization = ctx.headers.authorization;
  const token = authorization.replace('Bearer ', '')
  // 2.验证token(id/name/iat/exp)
  try{
    const result = jwt.verify(token, PUBLIC_KEY, { algorithms: ['RS256'] })
    ctx.user = result
    await next()
  }catch(err){  
    const error = new Error(errorTypes.UNAUTHORIZATION)
    ctx.app.emit('error',error, ctx)
  }
}
module.exports = { verifyLogin, verifyAuth }
  • 在controller/auth.controller.js中创建success控制器函数
javascript
/*---------------------controller/auth.controller.js-----------------------*/
const jwt = require("jsonwebtoken")

const { PRIVATE_KEY, PUBLIC_KEY } = require('../app/config')

class AuthController {
  async login(ctx, next) {
    const { id, name } = ctx.user

    // 颁发令牌
    const token = jwt.sign({ id, name }, PRIVATE_KEY, {
      expiresIn: 60 * 60 * 24,
      algorithm: 'RS256'
    })

    ctx.body = { id, name, token };

  }

  async success(ctx, next) {
    ctx.body = ctx.user
  }
}

module.exports = new AuthController()
  • 在constants/err-types.js中定义一个错误常量
javascript
/*------------------------constants/err-types.js--------------------*/
const NAME_OR_PASSWORD_IS_REQUIRED = 'name_or_password_is_required';
const USER_ALREADY_EXISTS = 'user_already_exists';
const USER_DOES_NOT_EXISTS = 'user_does_not_exists';
const PASSWORD_IS_INCORRENT = 'password_is_incorrent';
const UNAUTHORIZATION = 'unauthorzation'

module.exports = {
  NAME_OR_PASSWORD_IS_REQUIRED,
  USER_ALREADY_EXISTS,
  USER_DOES_NOT_EXISTS,
  PASSWORD_IS_INCORRENT,
  UNAUTHORIZATION
}
  • 在app/error.handle.js中编写错误处理代码
javascript
/*-------------------------app/error.handle.js---------------------*/
const errorTypes = require('../constants/error.types')

const errorHandler = (error, ctx) => {
  let status, message;
  switch (error.message) {
    case errorTypes.NAME_OR_PASSWORD_IS_REQUIRED:
      status = 400; // Bad Request
      message = "用户名或者密码不能为空~"
      break;
    case errorTypes.USER_ALREADY_EXISTS:
      status = 409; // conflict 
      message = "用户名已经存在~"
      break;
    case errorTypes.USER_DOES_NOT_EXISTS:
      status = 400; // 参数错误
      message = "用户名不存在~"
      break;
    case errorTypes.PASSWORD_IS_INCORRENT:
      status = 400; // 参数错误
      message = "密码是错误的~"
      break;
    case errorTypes.UNAUTHORIZATION:
      status = 401; 
      message = "无效的token~"
      break;
    default:
      status = 404;
      message = "NOT FOUND";
  }

  ctx.status = status;
  ctx.body = message;
}

module.exports = errorHandler

第七节-发布动态-发布动态的逻辑处理

  • 在router文件夹中新建moment.router.js文件
javascript
/*-----------------router/moment.router.js---------------*/
const Router = require('koa-router')

const momentRouter = new Router({ prefix: '/moment' })

const { verifyAuth } = require('../middleware/auth.middleware')

const { create } = require('../controller/moment.controller.js')

momentRouter.post('/', verifyAuth, create)

module.exports = momentRouter
  • 在controller文件夹中新建moment.controller.js文件
javascript
/*-----------------controller/moment.controller.js------*/
class MomentController {
  async create(ctx,next){
    ctx.body = "发表动态成功~"
  }
}

module.exports = new MomentController()
  • 修改middleware/auth.middleware.js文件中的verifyAuth函数完善逻辑
javascript
/*------------------------middleware/auth.middleware.js------------------------*/
const jwt = require('jsonwebtoken')
const fs = require('fs')
const path = require('path')

const service = require("../service/user.servics")
const errorTypes = require('../constants/error.types')
const md5password = require('../utils/password-handle')

const { PUBLIC_KEY } = require('../app/config')

const verifyLogin = async (ctx, next) => {
  // 1.获取用户名和密码
  const { name, password } = ctx.request.body;

  // 2.判断用户名和密码是否为空
  if (!name || !password) {
    const error = new Error(errorTypes.NAME_OR_PASSWORD_IS_REQUIRED);
    return ctx.app.emit('error', error, ctx);
  }

  // 3.判断用户是否存在的
  const result = await service.getUserByName(name)
  const user = result[0]
  if (!user) {
    const error = new Error(errorTypes.USER_DOES_NOT_EXISTS);
    return ctx.app.emit('error', error, ctx);
  }

  // 4.判断密码是否和数据库中的密码是一致(加密)
  if (md5password(password) !== user.password) {
    const error = new Error(errorTypes.PASSWORD_IS_INCORRENT);
    return ctx.app.emit('error', error, ctx);
  }
  ctx.user = user
  await next()
}

const verifyAuth = async (ctx, next) => {
  console.log("验证授权的middleware");
  // 1.获取token
  const authorization = ctx.headers.authorization;
  if (!authorization) {
    const error = new Error(errorTypes.UNAUTHORIZATION)
    return ctx.app.emit('error', error, ctx)
  }
  const token = authorization.replace('Bearer ', '')
  // 2.验证token(id/name/iat/exp)
  try {
    const result = jwt.verify(token, PUBLIC_KEY, { algorithms: ['RS256'] })
    ctx.user = result
    await next()
  } catch (err) {
    const error = new Error(errorTypes.UNAUTHORIZATION)
    ctx.app.emit('error', error, ctx)
  }
}
module.exports = { verifyLogin, verifyAuth }

第八节:发布动态-插入动态到数据库

  • 在mysql中创建moment表(动态表)
sql
CREATE TABLE IF NOT EXISTS `moment` (
	id INT PRIMARY KEY AUTO_INCREMENT,
	content VARCHAR(1000) NOT NULL,
	user_id INT NOT NULL,
	createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
	updateAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
	FOREIGN KEY(user_id) REFERENCES users(id)
);
  • controller/moment.controller.js中创建MomentController类,定义create控制器方法,并导出实例
javascript
/*------------------------controller/moment.controller.js-----------------------*/
const momentService = require('../service/moment.service')

class MomentController {
  async create(ctx, next) {
    // 1.获取数据(user_id, content)
    const userId = ctx.user.id
    const content = ctx.request.body.content
    // 2.将数据插入到数据库
    const result = await momentService.create(userId,content)
    ctx.body = result
  }
}

module.exports = new MomentController()
  • 在service文件夹下创建moment.service.js文件
javascript
/*---------------------service/moment.service.js-------------------*/
const connection = require('../app/database')

class MomentService {
  async create(userId, content) {
    const statement = `INSERT INTO moment (content, user_id) VALUES (?, ?);`;
    const result = await connection.execute(statement, [content, userId]);
    // 将user存储到数据库中
    return result
  }
}

module.exports = new MomentService();

第九节:查询动态-查询单个动态信息

  • 在router/moment.router.js文件中创建查询get请求路由
javascript
/*-------------router/moment.router.js-----------*/
const Router = require('koa-router')

const momentRouter = new Router({ prefix: '/moment' })

const { verifyAuth } = require('../middleware/auth.middleware')

const { create, detail } = require('../controller/moment.controller.js')

momentRouter.post('/', verifyAuth, create)
momentRouter.get('/:momentId', detail)

module.exports = momentRouter
  • 在controller/moment.controller.js编写detail查询动态数据控制器函数
javascript
/*-------------------controller/moment.controller.js-----------------*/
const momentService = require('../service/moment.service')

class MomentController {
  async create(ctx, next) {
    // 1.获取数据(user_id, content)
    const userId = ctx.user.id
    const content = ctx.request.body.content
    // 2.将数据插入到数据库
    const result = await momentService.create(userId,content)
    ctx.body = result
  }
  async detail(ctx, next) {
    // 1.获取数据(momentId)
    const momentId = ctx.params.momentId
    // 2.根据id去查询这条数据
    const result = await momentService.getMomentById(momentId)
    ctx.body = result
  }
}

module.exports = new MomentController()
  • 在service/moment.service.js文件中编写getMomentById函数
javascript
/*----------------service/moment.service.js-------------*/
const connection = require('../app/database')

class MomentService {
  async create(userId, content) {
    const statement = `INSERT INTO moment (content, user_id) VALUES (?, ?);`;
    const result = await connection.execute(statement, [content, userId]);
    return result
  }
  async getMomentById(id) {
    const statement = `
      SELECT 
        m.id id,m.content content,m.createAt createTime, m.updateAt updateTime,
        JSON_OBJECT('id',u.id,'name',u.name) author
        FROM moment m
      LEFT JOIN users u ON m.user_id = u.id
      WHERE m.id = ?;
    `;
    const result = await connection.execute(statement, [id]);
    // 将user存储到数据库中
    return result[0]
  }
}

module.exports = new MomentService();

第十节:查询动态-查询动态列表信息

  • 在router/moment.router.js添加路由
javascript
const { create, detail, list } = require('../controller/moment.controller.js')
momentRouter.get('/', list)
  • 在controller/moment.controller.js编写list查询动态数据控制器函数
javascript
/*------------------controller/moment.controller--------------------*/
const momentService = require('../service/moment.service')

class MomentController {
  async create(ctx, next) {
    // 1.获取数据(user_id, content)
    const userId = ctx.user.id
    const content = ctx.request.body.content
    // 2.将数据插入到数据库
    const result = await momentService.create(userId, content)
    ctx.body = result
  }
  async detail(ctx, next) {
    // 1.获取数据(momentId)
    const momentId = ctx.params.momentId
    // 2.根据id去查询这条数据
    const result = await momentService.getMomentById(momentId)
    ctx.body = result
  }
  async list(ctx, next) {
    // 1.获取数据
    const { offset, size } = ctx.query
    // 2.查询列表
    const result = await momentService.getMomentList(offset, size)
    ctx.body = result
  }
}

module.exports = new MomentController()
  • 在service/moment.service.js文件中编写getMomentList函数
javascript
/*-------------------service/moment.service.js-----------------------*/
const connection = require('../app/database')

const sqlFragment = `
  SELECT 
      m.id id,m.content content,m.createAt createTime, m.updateAt updateTime,
      JSON_OBJECT('id',u.id,'name',u.name) author
      FROM moment m
    LEFT JOIN users u ON m.user_id = u.id
`

class MomentService {
  async create(userId, content) {
    const statement = `INSERT INTO moment (content, user_id) VALUES (?, ?);`;
    const result = await connection.execute(statement, [content, userId]);
    return result
  }
  async getMomentById(id) {
    const statement = `
      ${sqlFragment}
      WHERE m.id = ?;
    `;
    const result = await connection.execute(statement, [id]);
    // 将user存储到数据库中
    return result[0]
  }
  async getMomentList(offset, size) {
    const statement = `
      ${sqlFragment}
      LIMIT ?, ?;
    `;
    const result = await connection.execute(statement, [offset, size]);
    // 将user存储到数据库中
    return result[0]
  }
}

module.exports = new MomentService();

第三部分

第一节:修改动态-修改动态的逻辑

评论修改需要满足两个条件(两个中间件)

1.用户必须登录

2.验证登录的用户是否有具备权限去修改内容

  • 在router/moment.router.js文件中添加路由
    • 两个中间件,verifyAuth(用户登录),(verifyPermission)用户是否有权限修改动态
javascript
/*-----------------router/moment.router.js-------------------*/
const Router = require('koa-router')

const momentRouter = new Router({ prefix: '/moment' })

const { verifyAuth, verifyPermission } = require('../middleware/auth.middleware')

const { create, list, detail, update } = require('../controller/moment.controller.js')

momentRouter.post('/', verifyAuth, create)
momentRouter.get('/', list)
momentRouter.get('/:momentId', detail)
// 1.用户必须登录 2.用户具备权限
momentRouter.patch('/:momentId', verifyAuth, verifyPermission, update)

module.exports = momentRouter
  • 在middleware/auth.middleware.js文件中添加verifyPermission方法
javascript
const jwt = require('jsonwebtoken')
const fs = require('fs')
const path = require('path')

const userService = require("../service/user.servics")
const authService = require("../service/auth.service")
const errorTypes = require('../constants/error.types')
const md5password = require('../utils/password-handle')

const { PUBLIC_KEY } = require('../app/config')

const verifyLogin = async (ctx, next) => {
  // 1.获取用户名和密码
  const { name, password } = ctx.request.body;

  // 2.判断用户名和密码是否为空
  if (!name || !password) {
    const error = new Error(errorTypes.NAME_OR_PASSWORD_IS_REQUIRED);
    return ctx.app.emit('error', error, ctx);
  }

  // 3.判断用户是否存在的
  const result = await userService.getUserByName(name)
  const user = result[0]
  if (!user) {
    const error = new Error(errorTypes.USER_DOES_NOT_EXISTS);
    return ctx.app.emit('error', error, ctx);
  }

  // 4.判断密码是否和数据库中的密码是一致(加密)
  if (md5password(password) !== user.password) {
    const error = new Error(errorTypes.PASSWORD_IS_INCORRENT);
    return ctx.app.emit('error', error, ctx);
  }
  ctx.user = user
  await next()
}

const verifyAuth = async (ctx, next) => {
  console.log("验证授权的middleware");
  // 1.获取token
  const authorization = ctx.headers.authorization;
  if (!authorization) {
    const error = new Error(errorTypes.UNAUTHORIZATION)
    return ctx.app.emit('error', error, ctx)
  }
  const token = authorization.replace('Bearer ', '')
  // 2.验证token(id/name/iat/exp)
  try {
    const result = jwt.verify(token, PUBLIC_KEY, {
      algorithms: ["RS256"]
    });
    ctx.user = result;
    await next();
  } catch (err) {
    const error = new Error(errorTypes.UNAUTHORIZATION);
    ctx.app.emit('error', error, ctx);
  }
}

const verifyPermission = async (ctx, next) => {
  console.log("验证权限的middleware~");

  // 1.获取参数
  const { momentId } = ctx.params
  const { id } = ctx.user
  const { content } = ctx.request.body

  // 2.查询是否具有权限
  try {
    const isPermission = await authService.checkMoment(momentId, id)
    if (!isPermission) throw new Error();
    await next()
  } catch (err) {
    const error = new Error(errorTypes.UNPERMISSION)
    return ctx.app.emit('error', error, ctx)
  }
}

module.exports = { verifyLogin, verifyAuth, verifyPermission }
  • 在service文件夹下新建auth.service.js文件单独封装权限查询的service
javascript
/*---------------service/auth.service.js--------------*/
const connection = require('../app/database')

class AuthService {
  async checkMoment(momentId, userId) {
    const statement = `
        SELECT * FROM moment WHERE id = ? AND user_id = ?;
      `
    const [result] = await connection.execute(statement, [momentId, userId])
    return result.length === 0 ? false : true
  }

  async checkComment() {

  }
}

module.exports = new AuthService()
  • 在service/moment.service.js文件中编写update方法
javascript
/*-------------------------------------*/
const connection = require('../app/database')

const sqlFragment = `
  SELECT 
      m.id id,m.content content,m.createAt createTime, m.updateAt updateTime,
      JSON_OBJECT('id',u.id,'name',u.name) author
      FROM moment m
    LEFT JOIN users u ON m.user_id = u.id
`

class MomentService {
  async create(userId, content) {
    const statement = `INSERT INTO moment (content, user_id) VALUES (?, ?);`;
    const result = await connection.execute(statement, [content, userId]);
    return result
  }
  async getMomentById(id) {
    const statement = `
      ${sqlFragment}
      WHERE m.id = ?;
    `;
    const result = await connection.execute(statement, [id]);
    // 将user存储到数据库中
    return result[0]
  }
  async getMomentList(offset, size) {
    const statement = `
      ${sqlFragment}
      LIMIT ?, ?;
    `;
    const result = await connection.execute(statement, [offset, size]);
    // 将user存储到数据库中
    return result[0]
  }

  async update(content, momentId) {
    const statement = `
      UPDATE moment SET content = ? WHERE id = ?;
    `;
    const [result] = await connection.execute(statement, [content, momentId])
    return result
  }
}

module.exports = new MomentService();
  • 在controller/moment.controller.js文件中编写update控制器方法
javascript
/*---------------controller/moment.controller.js---------------*/
const momentService = require('../service/moment.service')

class MomentController {
  async create(ctx, next) {
    // 1.获取数据(user_id, content)
    const userId = ctx.user.id
    const content = ctx.request.body.content
    // 2.将数据插入到数据库
    const result = await momentService.create(userId, content)
    ctx.body = result
  }
  async detail(ctx, next) {
    // 1.获取数据(momentId)
    const momentId = ctx.params.momentId
    // 2.根据id去查询这条数据
    const result = await momentService.getMomentById(momentId)
    ctx.body = result
  }
  async list(ctx, next) {
    // 1.获取数据
    const { offset, size } = ctx.query
    // 2.查询列表
    const result = await momentService.getMomentList(offset, size)
    ctx.body = result
  }

  async update(ctx, next) {
    // 1.获取参数
    const { momentId } = ctx.params
    const { content } = ctx.request.body

    // 2.修改内容
    const result = await momentService.update(content, momentId)
    ctx.body = '修改内容' + momentId + content + id
  }
}

module.exports = new MomentController()
  • 创建错误常量UNPERMISSION和处理代码
javascript
/*---------------------------constants/error.types.js------------------------*/
const NAME_OR_PASSWORD_IS_REQUIRED = 'name_or_password_is_required';
const USER_ALREADY_EXISTS = 'user_already_exists';
const USER_DOES_NOT_EXISTS = 'user_does_not_exists';
const PASSWORD_IS_INCORRENT = 'password_is_incorrent';
const UNAUTHORIZATION = 'unauthorzation'
const UNPERMISSION = 'unpermission'

module.exports = {
  NAME_OR_PASSWORD_IS_REQUIRED,
  USER_ALREADY_EXISTS,
  USER_DOES_NOT_EXISTS,
  PASSWORD_IS_INCORRENT,
  UNAUTHORIZATION,
  UNPERMISSION
}
javascript
/*---------------------------app/error.handle.js------------------------*/
const errorTypes = require('../constants/error.types')

const errorHandler = (error, ctx) => {

  let status, message;

  switch (error.message) {
    case errorTypes.NAME_OR_PASSWORD_IS_REQUIRED:
      status = 400; // Bad Request
      message = "用户名或者密码不能为空~"
      break;
    case errorTypes.USER_ALREADY_EXISTS:
      status = 409; // conflict 
      message = "用户名已经存在~"
      break;
    case errorTypes.USER_DOES_NOT_EXISTS:
      status = 400; // 参数错误
      message = "用户名不存在~"
      break;
    case errorTypes.PASSWORD_IS_INCORRENT:
      status = 400; // 参数错误
      message = "密码是错误的~"
      break;
    case errorTypes.UNAUTHORIZATION:
      status = 401; 
      message = "无效的token~"
      break;
    case errorTypes.UNPERMISSION:
      status = 401
      message = "您不具备操作的权限~"
      break
    default:
      status = 404;
      message = "NOT FOUND";
  }

  ctx.status = status;
  ctx.body = message;
}

module.exports = errorHandler

第二节:删除动态-删除动态的逻辑

  • 在router/moment.router.js文件中添加delete请求路由
javascript
/*---------------router/moment.router.js------------------*/
const Router = require('koa-router')

const momentRouter = new Router({ prefix: '/moment' })

const { verifyAuth, verifyPermission } = require('../middleware/auth.middleware')

const { create, list, detail, update, remove } = require('../controller/moment.controller.js')

momentRouter.post('/', verifyAuth, create)
momentRouter.get('/', list)
momentRouter.get('/:momentId', detail)
// 1.用户必须登录 2.用户具备权限
momentRouter.patch('/:momentId', verifyAuth, verifyPermission, update)
momentRouter.delete('/:momentId', verifyAuth, verifyPermission, remove)

module.exports = momentRouter
  • 在controller/moment.controller.js编写remove控制器函数
javascript
/*---------------controller/moment.controller.js---------------------*/
const momentService = require('../service/moment.service')

class MomentController {
  async create(ctx, next) {
    // 1.获取数据(user_id, content)
    const userId = ctx.user.id
    const content = ctx.request.body.content
    // 2.将数据插入到数据库
    const result = await momentService.create(userId, content)
    ctx.body = result
  }
  async detail(ctx, next) {
    // 1.获取数据(momentId)
    const momentId = ctx.params.momentId
    // 2.根据id去查询这条数据
    const result = await momentService.getMomentById(momentId)
    ctx.body = result
  }
  async list(ctx, next) {
    // 1.获取数据
    const { offset, size } = ctx.query
    // 2.查询列表
    const result = await momentService.getMomentList(offset, size)
    ctx.body = result
  }

  async update(ctx, next) {
    // 1.获取参数
    const { momentId } = ctx.params
    const { content } = ctx.request.body

    // 2.修改内容
    const result = await momentService.update(content, momentId)
    ctx.body = '修改内容' + momentId + content + id
  }

  async remove(ctx, next) {
    // 1.获取momentId
    const { momentId } = ctx.params

    // 2.删除内容
    const result = await momentService.remove(momentId)
    ctx.body = result
  }
}

module.exports = new MomentController()
  • 在service/moment.service.js中编写remove函数
javascript
/*----------------------service/moment.service.js---------------------*/
const connection = require('../app/database')

const sqlFragment = `
  SELECT 
      m.id id,m.content content,m.createAt createTime, m.updateAt updateTime,
      JSON_OBJECT('id',u.id,'name',u.name) author
      FROM moment m
    LEFT JOIN users u ON m.user_id = u.id
`

class MomentService {
  async create(userId, content) {
    const statement = `INSERT INTO moment (content, user_id) VALUES (?, ?);`;
    const result = await connection.execute(statement, [content, userId]);
    return result
  }
  async getMomentById(id) {
    const statement = `
      ${sqlFragment}
      WHERE m.id = ?;
    `;
    const result = await connection.execute(statement, [id]);
    // 将user存储到数据库中
    return result[0]
  }
  async getMomentList(offset, size) {
    const statement = `
      ${sqlFragment}
      LIMIT ?, ?;
    `;
    const result = await connection.execute(statement, [offset, size]);
    // 将user存储到数据库中
    return result[0]
  }

  async update(content, momentId) {
    const statement = `
      UPDATE moment SET content = ? WHERE id = ?;
    `;
    const [result] = await connection.execute(statement, [content, momentId])
    return result
  }

  async remove(momentId){
    const statement = `
      DELETE FROM moment WHERE id = ?;
    `;
    const [result] = await connection.execute(statement, [momentId])
    return result
  }
}

module.exports = new MomentService();

第三节:评论接口-表分析和创建表

  • 创建评论表,字段如下
    • id
    • content
    • user_id (外键约束)
    • moment_id (外键约束)
    • comment_id(外键约束comment_id 或者 null)
    • updateAt
    • createAt
sql
CREATE TABLE IF NOT EXISTS `comment`(
	id INT PRIMARY KEY AUTO_INCREMENT,
	content VARCHAR(1000) NOT NULL,
	moment_id INT NOT NULL,
	user_id INT NOT NULL,
	comment_id INT DEFAULT NULL,
	createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
	updateAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 
	
	FOREIGN KEY(moment_id) REFERENCES moment(id) ON DELETE CASCADE ON UPDATE CASCADE,
	FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,
	FOREIGN KEY(comment_id) REFERENCES comment(id) ON DELETE CASCADE ON UPDATE CASCADE
);

第四节:发布评论-发表评论的逻辑

  • 在router文件夹中创建comment.router.js文件
javascript
/*---------------router/comment.router.js------------------*/
const Router = require('koa-router')

const {
  verifyAuth
} = require('../middleware/auth.middleware')

const { create } = require('../controller/comment.controller')

const commentRouter = new Router({ prefix: '/comment' })

commentRouter.post('/', verifyAuth, create)


module.exports = commentRouter
  • 在controller文件夹中创建comment.controller.js文件
javascript
/*--------------------controller/comment.controller.js---------------------*/
const service = require('../service/comment.service.js')

class CommentController {
  async create(ctx, next) {
    const { momentId, content } = ctx.request.body
    const { id } = ctx.user

    const result = await service.create(momentId, content, id)
    ctx.body = result
  }
}
module.exports = new CommentController()
  • service文件夹中创建comment.service.js文件
javascript
/*----------------------service/comment.service.js-------------------*/
const connection = require('../app/database')
class CommentService {
  async create(momentId, content, userId) {
    const statement = `
      INSERT INTO comment (content, moment_id,user_id) VALUES (?,?,?);
    `;
    const [result] = await connection.execute(statement, [content, momentId, userId])
    return result
  }
}

module.exports = new CommentService()

第五节:回复评论-回复评论的逻辑

  • 在router/comment.router.js文件中创建回复评论路由
    • 符合restful风格的接口
javascript
/*----------------router/comment.router.js----------------*/
const Router = require('koa-router')

const {
  verifyAuth
} = require('../middleware/auth.middleware')

const { create, reply } = require('../controller/comment.controller')

const commentRouter = new Router({ prefix: '/comment' })

commentRouter.post('/', verifyAuth, create)
commentRouter.post('/:commentId/reply', verifyAuth, reply)

module.exports = commentRouter
  • 在controller/moment.controller.js中创建reply控制器函数
javascript
/*-------------controller/moment.controller.js------------*/
const service = require('../service/comment.service.js')

class CommentController {
  async create(ctx, next) {
    const { momentId, content } = ctx.request.body
    const { id } = ctx.user

    const result = await service.create(momentId, content, id)
    ctx.body = result
  }
  async reply(ctx, next) {
    const { momentId, content } = ctx.request.body
    const { id } = ctx.user
    const { commentId } = ctx.params
    const result = await service.reply(momentId, content, id, commentId)
    ctx.body = result
  }
}
module.exports = new CommentController()
  • 在service/comment.service.js文件中创建reply操作数据库的函数
javascript
/*----------service/comment.service.js------------------*/
const connection = require('../app/database')
class CommentService {
  async create(momentId, content, userId) {
    const statement = `
      INSERT INTO comment (content, moment_id,user_id) VALUES (?,?,?);
    `;
    const [result] = await connection.execute(statement, [content, momentId, userId])
    return result
  }
  async reply(momentId, content, userId, commentId) {
    const statement = `
      INSERT INTO comment (content, moment_id,user_id,comment_id) VALUES (?,?,?,?);
    `;
    const [result] = await connection.execute(statement, [content, momentId, userId, commentId])
    return result
  }
}

module.exports = new CommentService()

第六节:修改评论-修改评论的逻辑

  • 在router/comment.router.js文件中创建修改评论路由
javascript
/*--------------router/comment.router.js---------------*/
const Router = require('koa-router')

const {
  verifyAuth,
  verifyPermission
} = require('../middleware/auth.middleware')

const { create, reply, update } = require('../controller/comment.controller')

const commentRouter = new Router({ prefix: '/comment' })

commentRouter.post('/', verifyAuth, create)
commentRouter.post('/:commentId/reply', verifyAuth, reply)

// 修改评论
commentRouter.patch('/:commentId', verifyAuth, update)

// 删除评论

module.exports = commentRouter
  • 在controller/comment.controller.js中编写update函数
javascript
/*---------------controller/comment.controller.js------------------*/
const service = require('../service/comment.service.js')

class CommentController {
  async create(ctx, next) {
    const { momentId, content } = ctx.request.body
    const { id } = ctx.user

    const result = await service.create(momentId, content, id)
    ctx.body = result
  }
  async reply(ctx, next) {
    const { momentId, content } = ctx.request.body
    const { id } = ctx.user
    const { commentId } = ctx.params
    const result = await service.reply(momentId, content, id, commentId)
    ctx.body = result
  }
  async update(ctx, next) {
    const { commentId } = ctx.params
    const { content } = ctx.request.body
    const result = await service.update(commentId, content)
    ctx.body = result
  }
}
module.exports = new CommentController()
  • 在serviice/comment.service.js文件中编写update函数
javascript
/*-------------------serviice/comment.service.js----------------------*/
const connection = require('../app/database')
class CommentService {
  async create(momentId, content, userId) {
    const statement = `
      INSERT INTO comment (content, moment_id,user_id) VALUES (?,?,?);
    `;
    const [result] = await connection.execute(statement, [content, momentId, userId])
    return result
  }
  async reply(momentId, content, userId, commentId) {
    const statement = `
      INSERT INTO comment (content, moment_id,user_id,comment_id) VALUES (?,?,?,?);
    `;
    const [result] = await connection.execute(statement, [content, momentId, userId, commentId])
    return result
  }
  async update(commentId, content) {
    const statement = `
      UPDATE comment SET content = ? WHERE id = ?;
    `;
    const [result] = await connection.execute(statement, [content, commentId])
    return result
  }
}

module.exports = new CommentService()

第七节:权限认证-对所有的资源有效

  • 在router/comment.router.js修改评论路由中添加verifyPermission权限许可认证中间件
javascript
const Router = require('koa-router')

const {
  verifyAuth,
  verifyPermission
} = require('../middleware/auth.middleware')

const { create, reply, update } = require('../controller/comment.controller')

const commentRouter = new Router({ prefix: '/comment' })

commentRouter.post('/', verifyAuth, create)
commentRouter.post('/:commentId/reply', verifyAuth, reply)

// 修改评论
commentRouter.patch('/:commentId', verifyAuth, verifyPermission, update)

// 删除评论
module.exports = commentRouter
  • 在middleware/auth.middleware.js文件中修改verifyPermission中间件
    • 提供两种思路,一种使用闭包传递表名字符串
    • 一种根据restful风格可以获取表名
javascript
/*--------------------*/
const jwt = require('jsonwebtoken')
const fs = require('fs')
const path = require('path')

const userService = require("../service/user.servics")
const authService = require("../service/auth.service")
const errorTypes = require('../constants/error.types')
const md5password = require('../utils/password-handle')

const { PUBLIC_KEY } = require('../app/config')

const verifyLogin = async (ctx, next) => {
  // 1.获取用户名和密码
  const { name, password } = ctx.request.body;

  // 2.判断用户名和密码是否为空
  if (!name || !password) {
    const error = new Error(errorTypes.NAME_OR_PASSWORD_IS_REQUIRED);
    return ctx.app.emit('error', error, ctx);
  }

  // 3.判断用户是否存在的
  const result = await userService.getUserByName(name)
  const user = result[0]
  if (!user) {
    const error = new Error(errorTypes.USER_DOES_NOT_EXISTS);
    return ctx.app.emit('error', error, ctx);
  }

  // 4.判断密码是否和数据库中的密码是一致(加密)
  if (md5password(password) !== user.password) {
    const error = new Error(errorTypes.PASSWORD_IS_INCORRENT);
    return ctx.app.emit('error', error, ctx);
  }
  ctx.user = user
  await next()
}

const verifyAuth = async (ctx, next) => {
  console.log("验证授权的middleware");
  // 1.获取token
  const authorization = ctx.headers.authorization;
  if (!authorization) {
    const error = new Error(errorTypes.UNAUTHORIZATION)
    return ctx.app.emit('error', error, ctx)
  }
  const token = authorization.replace('Bearer ', '')
  // 2.验证token(id/name/iat/exp)
  try {
    const result = jwt.verify(token, PUBLIC_KEY, {
      algorithms: ["RS256"]
    });
    ctx.user = result;
    await next();
  } catch (err) {
    const error = new Error(errorTypes.UNAUTHORIZATION);
    ctx.app.emit('error', error, ctx);
  }
}

/**
 * 1.很多的内容都需要验证权限:修改/删除动态,修改/删除评论
 * 2.接口:业务接口系统/后端管理系统
 *  一对一:user -> role
 *  多对多:role -> memu(删除动态/修改动态)
 */
/* const verifyPermission = (tableName) => {
  return async (ctx, next) => {
    console.log("验证权限的middleware~");
  
    // 1.获取参数
    const { momentId } = ctx.params
    const { id } = ctx.user
    const { content } = ctx.request.body
  
    // 2.查询是否具有权限
    try {
      const isPermission = await authService.checkResource(tableName, momentId, id)
      if (!isPermission) throw new Error();
      await next()
    } catch (err) {
      const error = new Error(errorTypes.UNPERMISSION)
      return ctx.app.emit('error', error, ctx)
    }
  }
} */

const verifyPermission = async (ctx, next) => {
  console.log("验证权限的middleware~");

  // 1.获取参数 { commentId: '1' }
  const [resourceKey] = Object.keys(ctx.params)
  const tableName = resourceKey.replace('Id','')
  const resourceId = ctx.params[resourceKey]
  const { id } = ctx.user

  // 2.查询是否具有权限
  try {
    const isPermission = await authService.checkResource(tableName, resourceId, id)
    if (!isPermission) throw new Error();
    await next()
  } catch (err) {
    const error = new Error(errorTypes.UNPERMISSION)
    return ctx.app.emit('error', error, ctx)
  }
}

module.exports = { verifyLogin, verifyAuth, verifyPermission }
  • 在service/auth.service.js文件中把checkMoment修改为checkResource
javascript
/*----------------service/auth.service.js----------------------*/
const connection = require('../app/database')

class AuthService {
  async checkResource(tableName,id, userId) {
    const statement = `
        SELECT * FROM ${tableName} WHERE id = ? AND user_id = ?;
      `
    const [result] = await connection.execute(statement, [id, userId])
    return result.length === 0 ? false : true
  }
}

module.exports = new AuthService()

第八节:删除评论-删除评论的逻辑

  • 在router/comment.router.js文件中添加删除评论路由
javascript
/*--------------router/comment.router.js-----------*/
const Router = require('koa-router')

const {
  verifyAuth,
  verifyPermission
} = require('../middleware/auth.middleware')

const { create, reply, update } = require('../controller/comment.controller')

const commentRouter = new Router({ prefix: '/comment' })

commentRouter.post('/', verifyAuth, create)
commentRouter.post('/:commentId/reply', verifyAuth, reply)

// 修改评论
commentRouter.patch('/:commentId', verifyAuth, verifyPermission, update)

// 删除评论
commentRouter.delete('/:commentId', verifyAuth, verifyPermission, remove)

module.exports = commentRouter
  • 在controller/comment.controller.js文件中添加remove控制器函数
javascript
/*----------------------controller/comment.controller.js------------------------*/
const service = require('../service/comment.service.js')

class CommentController {
  async create(ctx, next) {
    const { momentId, content } = ctx.request.body
    const { id } = ctx.user

    const result = await service.create(momentId, content, id)
    ctx.body = result
  }
  async reply(ctx, next) {
    const { momentId, content } = ctx.request.body
    const { id } = ctx.user
    const { commentId } = ctx.params
    const result = await service.reply(momentId, content, id, commentId)
    ctx.body = result
  }
  async update(ctx, next) {
    const { commentId } = ctx.params
    const { content } = ctx.request.body
    const result = await service.update(commentId, content)
    ctx.body = result
  }
  async remove(ctx, next) {
    const { commentId } = ctx.params
    const result = await service.remove(commentId)
    ctx.body = result
  }
}
module.exports = new CommentController()
  • 在service/comment.service.js文件中添加remove方法
javascript
/*------------service/comment.service.js----------*/
const connection = require('../app/database')
class CommentService {
  async create(momentId, content, userId) {
    const statement = `
      INSERT INTO comment (content, moment_id,user_id) VALUES (?,?,?);
    `;
    const [result] = await connection.execute(statement, [content, momentId, userId])
    return result
  }
  async reply(momentId, content, userId, commentId) {
    const statement = `
      INSERT INTO comment (content, moment_id,user_id,comment_id) VALUES (?,?,?,?);
    `;
    const [result] = await connection.execute(statement, [content, momentId, userId, commentId])
    return result
  }
  async update(commentId, content) {
    const statement = `
      UPDATE comment SET content = ? WHERE id = ?;
    `;
    const [result] = await connection.execute(statement, [content, commentId])
    return result
  }
  async remove(commentId) {
    const statement = `
      DELETE FROM comment WHERE id = ?;
    `;
    const [result] = await connection.execute(statement, [commentId])
    return result
  }
}

module.exports = new CommentService()

第四部分

第一节:评论展示-动态列表中展示评论个数

  • 在service/moment.service.js文件中修改getMomentList函数的sql语句可以获取动态的评论数量(子查询)
javascript
/*--------------service/moment.service.js-------------------*/
const connection = require('../app/database')

/* const sqlFragment = `
  SELECT 
      m.id id,m.content content,m.createAt createTime, m.updateAt updateTime,
      JSON_OBJECT('id',u.id,'name',u.name) author
      FROM moment m
    LEFT JOIN users u ON m.user_id = u.id
` */

class MomentService {
  async create(userId, content) {
    const statement = `INSERT INTO moment (content, user_id) VALUES (?, ?);`;
    const result = await connection.execute(statement, [content, userId]);
    return result
  }
  async getMomentById(id) {
    const statement = `
      SELECT 
        m.id id,m.content content,m.createAt createTime, m.updateAt updateTime,
        JSON_OBJECT('id',u.id,'name',u.name) author
        FROM moment m
      LEFT JOIN users u ON m.user_id = u.id
      WHERE m.id = ?;
    `;
    const result = await connection.execute(statement, [id]);
    // 将user存储到数据库中
    return result[0]
  }
  async getMomentList(offset, size) {
    const statement = `
      SELECT 
        m.id, m.content, m.createAt, m.updateAt, JSON_OBJECT("id",u.id,"name",u.name) author, 
        (SELECT COUNT(*) FROM comment c WHERE c.moment_id = m.id) commentCount
        FROM moment m
      LEFT JOIN users u ON m.user_id = u.id
      LIMIT 0,5
  ;
    `;
    const result = await connection.execute(statement, [offset, size]);
    // 将user存储到数据库中
    return result[0]
  }

  async update(content, momentId) {
    const statement = `
      UPDATE moment SET content = ? WHERE id = ?;
    `;
    const [result] = await connection.execute(statement, [content, momentId])
    return result
  }

  async remove(momentId) {
    const statement = `
      DELETE FROM moment WHERE id = ?;
    `;
    const [result] = await connection.execute(statement, [momentId])
    return result
  }
}

module.exports = new MomentService();

第二节:评论展示-单独接口获取评论列表

  • 在router/comment.router.js文件中添加获取评论列表路由
javascript
/*----------------router/comment.router.js----------------*/
const Router = require('koa-router')

const {
  verifyAuth,
  verifyPermission
} = require('../middleware/auth.middleware')

const { create, reply, update, remove, list } = require('../controller/comment.controller')

const commentRouter = new Router({ prefix: '/comment' })

// 发表评论
commentRouter.post('/', verifyAuth, create)
commentRouter.post('/:commentId/reply', verifyAuth, reply)

// 修改评论
commentRouter.patch('/:commentId', verifyAuth, verifyPermission, update)

// 删除评论
commentRouter.delete('/:commentId', verifyAuth, verifyPermission, remove)

// 获取评论列表
commentRouter.get('/', list)

module.exports = commentRouter
  • 在controller/comment.controller.js文件中添加list控制器函数
javascript
/*----------------controller/comment.controller.js--------------------*/
const service = require('../service/comment.service.js')

class CommentController {
  async create(ctx, next) {
    const { momentId, content } = ctx.request.body
    const { id } = ctx.user

    const result = await service.create(momentId, content, id)
    ctx.body = result
  }
  async reply(ctx, next) {
    const { momentId, content } = ctx.request.body
    const { id } = ctx.user
    const { commentId } = ctx.params
    const result = await service.reply(momentId, content, id, commentId)
    ctx.body = result
  }
  async update(ctx, next) {
    const { commentId } = ctx.params
    const { content } = ctx.request.body
    const result = await service.update(commentId, content)
    ctx.body = result
  }
  async remove(ctx, next) {
    const { commentId } = ctx.params
    const result = await service.remove(commentId)
    ctx.body = result
  }

  async list(ctx, next) {
    const { momentId } = ctx.query
    const result = await service.getCommentsByMomentId(momentId)
    ctx.body = result
  }
}
module.exports = new CommentController()
  • 在service/comment.service.js文件中编写getCommentsByMomentId函数
javascript
/*--------------------service/comment.service.js---------------------*/
const connection = require('../app/database')
class CommentService {
  async create(momentId, content, userId) {
    const statement = `
      INSERT INTO comment (content, moment_id,user_id) VALUES (?,?,?);
    `;
    const [result] = await connection.execute(statement, [content, momentId, userId])
    return result
  }
  async reply(momentId, content, userId, commentId) {
    const statement = `
      INSERT INTO comment (content, moment_id,user_id,comment_id) VALUES (?,?,?,?);
    `;
    const [result] = await connection.execute(statement, [content, momentId, userId, commentId])
    return result
  }
  async update(commentId, content) {
    const statement = `
      UPDATE comment SET content = ? WHERE id = ?;
    `;
    const [result] = await connection.execute(statement, [content, commentId])
    return result
  }
  async remove(commentId) {
    const statement = `
      DELETE FROM comment WHERE id = ?;
    `;
    const [result] = await connection.execute(statement, [commentId])
    return result
  }
  async getCommentsByMomentId(momentId){
    const statement = `
      SELECT * FROM comment WHERE moment_id = ?
    `;
    const [result] = await connection.execute(statement,[momentId])
    return result
  }
}

module.exports = new CommentService()

第三节:评论展示-动态详情获取评论列表

  • 在service/moment.service.js文件中修改getMomentById函数
javascript
/*------------------service/moment.service.js-------------------------*/
const connection = require('../app/database')

/* const sqlFragment = `
  SELECT 
      m.id id,m.content content,m.createAt createTime, m.updateAt updateTime,
      JSON_OBJECT('id',u.id,'name',u.name) author
      FROM moment m
    LEFT JOIN users u ON m.user_id = u.id
` */

class MomentService {
  async create(userId, content) {
    const statement = `INSERT INTO moment (content, user_id) VALUES (?, ?);`;
    const result = await connection.execute(statement, [content, userId]);
    return result
  }
  async getMomentById(id) {
    const statement = `
      SELECT 
        m.id,m.content,m.createAt,m.updateAt,JSON_OBJECT("id",u.id,"name",u.name) author,
        JSON_ARRAYAGG(JSON_OBJECT("id",c.id,"content",c.content,"createTime",c.createAt,
        "user",JSON_OBJECT("id",cu.id,"name",cu.name)
        )) comments
      FROM moment m
      LEFT JOIN users u ON m.user_id = u.id
      LEFT JOIN comment c ON c.moment_id = m.id
      LEFT JOIN users cu ON c.user_id = cu.id
      WHERE m.id = 2;
    `;
    const result = await connection.execute(statement, [id]);
    // 将user存储到数据库中
    return result[0]
  }
  async getMomentList(offset, size) {
    const statement = `
      SELECT 
        m.id, m.content, m.createAt, m.updateAt, JSON_OBJECT("id",u.id,"name",u.name) author, 
        (SELECT COUNT(*) FROM comment c WHERE c.moment_id = m.id) commentCount
        FROM moment m
      LEFT JOIN users u ON m.user_id = u.id
      LIMIT 0,5
  ;
    `;
    const result = await connection.execute(statement, [offset, size]);
    // 将user存储到数据库中
    return result[0]
  }

  async update(content, momentId) {
    const statement = `
      UPDATE moment SET content = ? WHERE id = ?;
    `;
    const [result] = await connection.execute(statement, [content, momentId])
    return result
  }

  async remove(momentId) {
    const statement = `
      DELETE FROM moment WHERE id = ?;
    `;
    const [result] = await connection.execute(statement, [momentId])
    return result
  }
}

module.exports = new MomentService();
  • 在service/comment.service.js文件中修改getCommentsByMomentId函数
javascript
/*------------------service/comment.service.js-------------------------*/
const connection = require('../app/database')
class CommentService {
  async create(momentId, content, userId) {
    const statement = `
      INSERT INTO comment (content, moment_id,user_id) VALUES (?,?,?);
    `;
    const [result] = await connection.execute(statement, [content, momentId, userId])
    return result
  }
  async reply(momentId, content, userId, commentId) {
    const statement = `
      INSERT INTO comment (content, moment_id,user_id,comment_id) VALUES (?,?,?,?);
    `;
    const [result] = await connection.execute(statement, [content, momentId, userId, commentId])
    return result
  }
  async update(commentId, content) {
    const statement = `
      UPDATE comment SET content = ? WHERE id = ?;
    `;
    const [result] = await connection.execute(statement, [content, commentId])
    return result
  }
  async remove(commentId) {
    const statement = `
      DELETE FROM comment WHERE id = ?;
    `;
    const [result] = await connection.execute(statement, [commentId])
    return result
  }
  async getCommentsByMomentId(momentId){
    const statement = `
      SELECT c.id,c.content,c.comment_id commentId, c.createAt createTime, 
        JSON_OBJECT('id', u.id,'name', u.name) author
      FROM comment c
      LEFT JOIN users u ON c.user_id = u.id
      WHERE moment_id = ?;
    `;
    const [result] = await connection.execute(statement,[momentId])
    return result
  }
}

module.exports = new CommentService()

第四节:创建标签-定义直接创建标签接口

  • 创建标签表
sql
CREATE TABLE IF NOT EXISTS `lable`(
	id INT PRIMARY KEY AUTO_INCREMENT,
	name VARCHAR(10) NOT NULL UNIQUE,
	createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
	updateAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
  • 在router文件夹中创建label.router.js文件
javascript
/*------------router/label.router.js-------------*/
const Router = require('koa-router')

const {
  verifyAuth
} = require('../middleware/auth.middleware')

const {
  create
} = require('../controller/label.controller.js')

const labelRouter = new Router({ prefix: '/label' })

labelRouter.post('/',verifyAuth, create)

module.exports = labelRouter
  • 在controller文件夹中创建label.controller.js文件
javascript
/*---------------controller/label.controller.js-------------------*/
const service = require('../service/label.service')

class LaberController {
  async create(ctx, next) {
    const { name } = ctx.request.body
    const result = await service.create(name)
    ctx.body = result
  }
}

module.exports = new LaberController()
  • 在service文件中创建label.service.js文件
javascript
/*-----------service/label.service.js------------*/
const connection = require('../app/database')
class LabelService {
  async create(name){
    const statement = `INSERT INTO label (name) VALUES (?);`;
    const [result] = await connection.execute(statement, [name]);
    return result;
  }
}

module.exports = new LabelService()

第五节:动态标签-给动态添加标签

  • 创建动态和标签的关系表
sql
CREATE TABLE IF NOT EXISTS `moment_label` (
	moment_id INT NOT NULL,
	label_id INT NOT NULL,
	createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
	updateAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
	PRIMARY KEY(moment_id,label_id),
	FOREIGN KEY(moment_id) REFERENCES moment(id) ON DELETE CASCADE ON UPDATE CASCADE,
	FOREIGN KEY(label_id) REFERENCES label(id) ON DELETE CASCADE ON UPDATE CASCADE
);
  • 在router/moment.router.js文件中添加路由
  • 定义接口
    • 作用:给动态添加标签
    • 请求:POST
    • 接口:moment/1/labels
    • 参数:labels
    • 例子:body{ labels:["前端"] }
    • 数据:{}
javascript
/*-------------router/moment.router.js--------------*/
const Router = require('koa-router')

const momentRouter = new Router({ prefix: '/moment' })

const { verifyAuth, verifyPermission } = require('../middleware/auth.middleware')

const { create, list, detail, update, remove, addLabels } = require('../controller/moment.controller.js')

momentRouter.post('/', verifyAuth, create)
momentRouter.get('/', list)
momentRouter.get('/:momentId', detail)
// 1.用户必须登录 2.用户具备权限
momentRouter.patch('/:momentId', verifyAuth, verifyPermission, update)
momentRouter.delete('/:momentId', verifyAuth, verifyPermission, remove)

// 给动态添加标签
momentRouter.post('/:momentId/labels', verifyAuth, verifyPermission,  addLabels)

module.exports = momentRouter
  • 在controller/moment.controller.js文件中创建控制器函数
javascript
/*----------------controller/moment.controller.js------------------*/
const momentService = require('../service/moment.service')

class MomentController {
  async create(ctx, next) {
    // 1.获取数据(user_id, content)
    const userId = ctx.user.id
    const content = ctx.request.body.content
    // 2.将数据插入到数据库
    const result = await momentService.create(userId, content)
    ctx.body = result
  }
  async detail(ctx, next) {
    // 1.获取数据(momentId)
    const momentId = ctx.params.momentId
    // 2.根据id去查询这条数据
    const result = await momentService.getMomentById(momentId)
    ctx.body = result
  }
  async list(ctx, next) {
    // 1.获取数据
    const { offset, size } = ctx.query
    // 2.查询列表
    const result = await momentService.getMomentList(offset, size)
    ctx.body = result
  }

  async update(ctx, next) {
    // 1.获取参数
    const { momentId } = ctx.params
    const { content } = ctx.request.body

    // 2.修改内容
    const result = await momentService.update(content, momentId)
    ctx.body = '修改内容' + momentId + content + id
  }

  async remove(ctx, next) {
    // 1.获取momentId
    const { momentId } = ctx.params

    // 2.删除内容
    const result = await momentService.remove(momentId)
    ctx.body = result
  }

  async addLabels(ctx, next) {
    const { labels } = ctx.request.body
    ctx.body = "给动态添加标签"
  }
}

module.exports = new MomentController()

第六节:创建标签-判断标签是否存在

  • 在middleware文件夹中创建label.middleware.js文件
javascript
/*------------------middleware/label.middleware.js----------------*/
const service = require('../service/label.service')
const verifyLabelExists = async (ctx, next) => {
  // 1.取出要添加的所有的标签
  const {labels} = ctx.request.body
  // 2.判断每一个标签在label表中是否存在
  const newLabels = []
  for(let name of labels) {
    const labelResult = await service.getLabelByName(name)
    const label = {name}
    if(!labelResult) {
      // 创建标签数据
      const result = await service.create(name)
      label.id = result.insertId
    } else {
      label.id = labelResult.id
    }
    newLabels.push(label)
  }
  ctx.labels = newLabels
  await next()
}

module.exports = {
  verifyLabelExists
}
  • 在service/label.service.js文件中创建getLabelByName函数
javascript
/*---------------service/label.service.js--------------------*/
const connection = require('../app/database')
class LabelService {
  async create(name){
    const statement = `INSERT INTO label (name) VALUES (?);`;
    const [result] = await connection.execute(statement, [name]);
    return result;
  }

  async getLabelByName(name) {
    const statement = `SELECT * FROM label WHERE name = ?;`
    const [result] = await connection.execute(statement, [name])
    return result[0]
  }
}

module.exports = new LabelService()
  • 在router/moment.router.js文件中引入并使用中间件
javascript
/*--------------router/moment.router.js-------------*/
const Router = require('koa-router')

const momentRouter = new Router({ prefix: '/moment' })

const { verifyAuth, verifyPermission } = require('../middleware/auth.middleware')

const { create, list, detail, update, remove, addLabels } = require('../controller/moment.controller.js')

const { verifyLabelExists } = require('../middleware/label.middleware.js')

momentRouter.post('/', verifyAuth, create)
momentRouter.get('/', list)
momentRouter.get('/:momentId', detail)
// 1.用户必须登录 2.用户具备权限
momentRouter.patch('/:momentId', verifyAuth, verifyPermission, update)
momentRouter.delete('/:momentId', verifyAuth, verifyPermission, remove)

// 给动态添加标签
momentRouter.post('/:momentId/labels', verifyAuth, verifyPermission, verifyLabelExists, addLabels)

module.exports = momentRouter

第七节:动态标签-给动态添加标签

  • 在controller/moment.controller.js文件中完成addLabels控制器函数
javascript
/*-------------controller/moment.controller.js------------*/
const momentService = require('../service/moment.service')

class MomentController {
  async create(ctx, next) {
    // 1.获取数据(user_id, content)
    const userId = ctx.user.id
    const content = ctx.request.body.content
    // 2.将数据插入到数据库
    const result = await momentService.create(userId, content)
    ctx.body = result
  }
  async detail(ctx, next) {
    // 1.获取数据(momentId)
    const momentId = ctx.params.momentId
    // 2.根据id去查询这条数据
    const result = await momentService.getMomentById(momentId)
    ctx.body = result
  }
  async list(ctx, next) {
    // 1.获取数据
    const { offset, size } = ctx.query
    // 2.查询列表
    const result = await momentService.getMomentList(offset, size)
    ctx.body = result
  }
  async update(ctx, next) {
    // 1.获取参数
    const { momentId } = ctx.params
    const { content } = ctx.request.body

    // 2.修改内容
    const result = await momentService.update(content, momentId)
    ctx.body = '修改内容' + momentId + content
  }
  async remove(ctx, next) {
    // 1.获取参数
    const { momentId } = ctx.params
    // 2.删除动态
    const result = await momentService.remove(momentId)
    ctx.body = '删除动态成功'
  }
  async addLabels(ctx, next) {
    // 1.获取标签和动态id
    const { labels } = ctx
    const { momentId } = ctx.params
    
    // 2.添加所有的标签
    for(let label of labels) {
      // 2.1判断标签是否已经和动态有关系
      const isExists = await momentService.hasLabel(momentId, label.id)
      if(!isExists){
        const result = await momentService.addLabel(momentId, label.id)
      }
    }
    ctx.body = '给动态添加标签成功~'
  }
}

module.exports = new MomentController()
  • 在service/moment.service.js文件中创建hasLabel和addLabel方法
javascript
/*------------------service/moment.service.js----------------------*/
const connection = require('../app/database')

/* const sqlFragment = `
  SELECT 
    m.id id,
    m.content content,
    m.createAt createAt,
    m.updateAt updateAt,
  JSON_OBJECT('id',u.id,'name',u.name) author 
  FROM moment m 
  LEFT JOIN users u ON m.user_id = u.id
` */

class MomentService {
  async create(userId, content) {
    const statement = `INSERT INTO moment (content, user_id) VALUES (?, ?);`
    const result = await connection.execute(statement, [content, userId])
    // 将user存储到数据库中
    return result
  }
  async getMomentById(id) {
    const statement = `
      SELECT 
        m.id,m.content,m.createAt,m.updateAt,
      JSON_OBJECT('id',u.id,'name',u.name) author, 
      JSON_ARRAYAGG(JSON_OBJECT('id',c.id,'content',c.content,'createTime',c.createAt,'user',JSON_OBJECT('id',cu.id,'name',cu.name))) comments
      FROM moment m 
      LEFT JOIN users u ON m.user_id = u.id
      LEFT JOIN comment c ON c.moment_id = m.id
      LEFT JOIN users cu ON c.user_id = cu.id
      WHERE m.id = ${id};
    `

    const result = await connection.execute(statement)
    // 将user存储到数据库中
    return result[0]
  }

  async getMomentList(offset, size) {
    const statement = `
      SELECT 
        m.id id,
        m.content content,
        m.createAt createAt,
        m.updateAt updateAt,
        JSON_OBJECT('id',u.id,'name',u.name) author,
        (SELECT COUNT(*) FROM comment c WHERE c.moment_id = m.id) commentCount
      FROM moment m
      LEFT JOIN users u ON m.user_id = u.id
      LIMIT ?, ?;
    `
    const result = await connection.execute(statement, [offset, size])
    return result[0]
  }
  async update(content, momentId) {
    const statement = `
      UPDATE moment SET content = ? WHERE id = ?;
    `
    const [result] = await connection.execute(statement, [content, momentId])
    return result
  }
  async remove(momentId) {
    const statement = `
      DELETE FROM moment WHERE id = ?;
    `
    const [result] = await connection.execute(statement, [momentId])
    return result
  }
  async hasLabel(momentId, labelId){
    const statement = `
      SELECT * FROM moment_label WHERE moment_id = ? AND label_id = ?;
    `
    const [result] = await connection.execute(statement,[momentId,labelId])
    return result[0] ? true : false
  }
  async addLabel(momentId, labelId){
    const statement = `
      INSERT INTO moment_label (moment_id, label_id) VALUES (?, ?);
    `
    const [result] = await connection.execute(statement,[momentId,labelId])
    return result 
  }
}

module.exports = new MomentService()

第八节:获取标签-获取标签列表接口

  • 在router/label.router.js文件中添加标签获取接口
javascript
/*--------------router/label.router.js------------*/
const Router = require('koa-router')
const { verifyAuth } = require('../middleware/auth.middleware')

const { create, list } = require('../controller/label.controller.js')

const labelRouter = new Router({ prefix: '/label' })

labelRouter.post('/', verifyAuth, create)
labelRouter.get('/', list)

module.exports = labelRouter
  • 在controller/label.controller.js文件中编写list控制器函数
javascript
/*--------------controller/label.controller.js------------------*/
const service = require('../service/label.service')
class LabelController {
  async create(ctx, next) {
    const { name } = ctx.request.body
    const result = await service.create(name)
    ctx.body = result
  }
  async list(ctx, next) {
    const { limit, offset } = ctx.query
    const result = await service.getLabels(limit, offset)
    ctx.body = result
  }
}

module.exports = new LabelController()
  • 在service/label.service文件中编写getLabels方法
javascript
/*-------------service/label.serveice.js-----------------*/
const connection = require('../app/database')

class LabelService  {
  async create(name){ 
    const statement = `
      INSERT INTO label (name) VALUES (?);
    `
    const [result] = await connection.execute(statement,[name])
    return result
  }
  async getLabelByName(name) {
    const statement = `
      SELECT * FROM label WHERE name = ?;
    `
    const [result] = await connection.execute(statement, [name])
    return result[0]
  }
  async getLabels(limit, offset){
    const statement = `
      SELECT * FROM label LIMIT ?, ?;
    `
    const [result] = await connection.execute(statement,[offset,limit])
    return result
  }
}
module.exports = new LabelService()

第九节:获取动态-同时获取接口信息

  • 获取动态列表接口的sql添加可以展示标签数量的字段
    • 在service/moment.service.js文件中把getMomentList中的statement修改为如下的sql查询语句即可
    • 也就是添加这一句(SELECT COUNT(*) FROM moment_label ml WHERE ml.moment_id = m.id) LabelCount
sql
const statement = `
      SELECT 
        m.id id,
        m.content content,
        m.createAt createAt,
        m.updateAt updateAt,
        JSON_OBJECT('id',u.id,'name',u.name) author,
        (SELECT COUNT(*) FROM comment c WHERE c.moment_id = m.id) commentCount,
        (SELECT COUNT(*) FROM moment_label ml WHERE ml.moment_id = m.id) LabelCount
      FROM moment m
      LEFT JOIN users u ON m.user_id = u.id
      LIMIT ?, ?;
    `
  • 获取单个动态时,结果显示labels字段
    • 在service/moment.service.js文件中把getMomentById中的statement修改为如下的sql查询语句即可
    • 也就是左连接,再左连接获得label数据
    • IF的作用是条件判断,类似三元表达式,可以让没有的数据返回一个null而不是返回一大堆null的字段
sql
/*有bug,会重复*/
const statement = `
      SELECT 
        m.id,m.content,m.createAt,m.updateAt,
      JSON_OBJECT('id',u.id,'name',u.name) author, 
      IF(COUNT(c.id),JSON_ARRAYAGG(JSON_OBJECT('id',c.id,'content',c.content,'createTime',c.createAt,'user',JSON_OBJECT('id',cu.id,'name',cu.name))),NULL) comments,
      IF(COUNT(l.id),JSON_ARRAYAGG(JSON_OBJECT('id',l.id,'name',l.name)),NULL) labels
      FROM moment m 
      LEFT JOIN users u ON m.user_id = u.id
      LEFT JOIN comment c ON c.moment_id = m.id
      LEFT JOIN users cu ON c.user_id = cu.id
      LEFT JOIN moment_label ml ON ml.moment_id = m.id
      LEFT JOIN label l ON ml.label_id = l.id
      WHERE m.id = ?
      GROUP BY m.id;
    `
sql
/*没bug,不会重复*/
const statement = `
      SELECT 
        m.id id, m.content content, m.createAt createTime, m.updateAt updateTime,
        JSON_OBJECT('id', u.id, 'name', u.name) author,
        IF(COUNT(l.id),JSON_ARRAYAGG(
          JSON_OBJECT('id', l.id, 'name', l.name)
        ),NULL) labels,
        (SELECT IF(COUNT(c.id),JSON_ARRAYAGG(
          JSON_OBJECT('id', c.id, 'content', c.content, 'commentId', c.comment_id, 'createTime', c.createAt,
                      'user', JSON_OBJECT('id', cu.id, 'name', cu.name))
        ),NULL) FROM comment c LEFT JOIN users cu ON c.user_id = cu.id WHERE m.id = c.moment_id) comments
      FROM moment m
      LEFT JOIN users u ON m.user_id = u.id
      LEFT JOIN moment_label ml ON m.id = ml.moment_id
      LEFT JOIN label l ON ml.label_id = l.id
      WHERE m.id = ?
      GROUP BY m.id;  
    `;

第五部分

第一节:上传头像-上传头像的图片处理

  • 在router文件夹中创建file.router.js文件,用来做文件上传路由
javascript
/*------------------router/file.router.js------------------*/
const Router = require("koa-router")

const { verifyAuth } = require('../middleware/auth.middleware')

const { avatarHandler } = require('../middleware/file.middleware')

const fileRouter = new Router({ prefix: '/upload' })

fileRouter.post('/avatar', verifyAuth, avatarHandler)


module.exports = fileRouter
  • npm-i koa-multer 安装koa文件处理库
  • 在middleware文件夹中创建file.middleware.js文件
javascript
/*--------------middleware/file.middleware.js-------------------*/
const Multer = require('koa-multer')

const avatarUpload = Multer({
  dest: './uploads/avatar'
})

const avatarHandler = avatarUpload.single('avatar')

module.exports = {
  avatarHandler
}

第二节:上传头像-保存头像信息到数据库

  • 在controller文件夹中创建file.controller.js文件,创建saveAvatarInfo控制器用于保存图片头像信息
  • 创建数据表保存图片信息
sql
CREATE TABLE IF NOT EXISTS avatar (
	id INT PRIMARY KEY AUTO_INCREMENT,
	filename VARCHAR(100) NOT NULL UNIQUE,
	mimetype VARCHAR(30),
	size INT,
	user_id INT,
	createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
	updateAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
	FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE
)
  • 在router/file.router.js文件中添加saveAvatarInfo控制器方法
javascript
/*------------router/file.router.js----------*/
const Router = require("koa-router")

const { verifyAuth } = require('../middleware/auth.middleware')

const { avatarHandler } = require('../middleware/file.middleware')

const { saveAvatarInfo } = require('../controller/file.controller.js')

const fileRouter = new Router({ prefix: '/upload' })

fileRouter.post('/avatar', verifyAuth, avatarHandler, saveAvatarInfo)


module.exports = fileRouter
  • 在在controller文件中创建file.controller.js文件
javascript
/*-------------controller.file.controller.js-------------*/
const fileService = require('../service/file.service.js')
class FileController {
  async saveAvatarInfo(ctx, next) {
    // 1.获取图像相关信息
    const { mimetype, filename, size } = ctx.req.file
    const {id} = ctx.user

    // 2.将图像信息数据保存到数据库中
    const result = await fileService.createAvatar(id, mimetype, filename, size)

    // 3.返回结果
    ctx.body = result
  }
}

module.exports = new FileController()
  • 在service文件夹中创建file.service.js文件
javascript
/*------------service/file.service.js----------*/
const connection = require('../app/database')

class FileService {
  async createAvatar(userId, mimetype, filename, size) {
    const statement = `
      INSERT INTO avatar (user_id, filename, mimetype,size) VALUES(?,?,?,?)
    `
    const [result] = await connection.execute(statement, [userId, filename, mimetype, size])
    return result
  }
}

module.exports = new FileService()

第三节:上传头像-提供展示图片的接口

  • 在router/user.router.js文件中添加查询用户头像路由
javascript
/*-----------------------router/user.router.js----------------------*/
const Router = require('koa-router')

const { create,avatarInfo } = require('../controller/user.controller.js')

const { verifyUser, handlePassword } = require('../middleware/user.middleware')

const userRouter = new Router({ prefix: '/users' })

userRouter.post('/', verifyUser, handlePassword, create)
userRouter.get('/:userId/avatar', avatarInfo)

module.exports = userRouter
  • 在controller/user.controller.js文件中创建avatarInfo控制器方法
javascript
/*----------------controller/uesr.controller.js-------------------*/
const fs = require('fs')
const userService = require('../service/user.service')
const fileService = require('../service/file.service')
const { AVATAR_PATH } = require('../constants/file-path')

class userController {
  async create(ctx, next) {
    // 获取用户请求传递的参数
    const user = ctx.request.body

    // 查询数据
    const result = await userService.create(user)

    // 返回数据
    ctx.body = fs.createReadStream(`./upload/`)
  }
  async avatarInfo(ctx, next) {
    const { userId } = ctx.params
    const avatarInfo = await fileService.getAvatarByUserId(userId)
    // 2.提供图像信息
    ctx.response.set('content-type', avatarInfo.mimetype)
    ctx.body = fs.createReadStream(`${AVATAR_PATH}/${avatarInfo.filename}`)
  }
}

module.exports = new userController()
  • 在service/file.service文件中创建getAvatarByUserId方法
javascript
const connection = require('../app/database')

class FileService {
  async createAvatar(userId, mimetype, filename, size) {
    const statement = `
      INSERT INTO avatar (user_id, filename, mimetype,size) VALUES(?,?,?,?)
    `
    const [result] = await connection.execute(statement, [userId, filename, mimetype, size])
    return result
  }
  async getAvatarByUserId(userId){
    const statement = `SELECT * FROM avatar WHERE user_id = ?;`
    const [result] = await connection.execute(statement,[userId])
    return result[0]
  }
}

module.exports = new FileService()
  • 在constants文件夹中创建file-path.js文件
javascript
/*----------constants/file-path.js------------*/
const AVATAR_PATH = './uploads/avatar'

module.exports = {
  AVATAR_PATH
}
  • middleware/file.middleware.js文件中也换成常量
javascript
/*-------------------middleware/file.middleware.js-----------*/
const Multer = require('koa-multer')

const { AVATAR_PATH } = require('../constants/file-path')

const avatarUpload = Multer({
  dest: AVATAR_PATH
})

const avatarHandler = avatarUpload.single('avatar')

module.exports = {
  avatarHandler
}

第四节:上传头像-将头像地址保存到user中

  • 用户表添加头像地址字段
sql
ALTER TABLE users ADD avatar_url VARCHAR(200);
  • 修改service/moment.service.js文件getMomentById函数的sql语句
sql
const statement = `
      SELECT 
        m.id id, m.content content, m.createAt createTime, m.updateAt updateTime,
        JSON_OBJECT('id', u.id, 'name', u.name, 'avatarUrl', u.avatar_url) author,
        IF(COUNT(l.id),JSON_ARRAYAGG(
          JSON_OBJECT('id', l.id, 'name', l.name)
        ),NULL) labels,
        (SELECT IF(COUNT(c.id),JSON_ARRAYAGG(
          JSON_OBJECT('id', c.id, 'content', c.content, 'commentId', c.comment_id, 'createTime', c.createAt,
                      'user', JSON_OBJECT('id', cu.id, 'name', cu.name, 'avatarUrl', cu.avatar_url))
        ),NULL) FROM comment c LEFT JOIN users cu ON c.user_id = cu.id WHERE m.id = c.moment_id) comments
      FROM moment m
      LEFT JOIN users u ON m.user_id = u.id
      LEFT JOIN moment_label ml ON m.id = ml.moment_id
      LEFT JOIN label l ON ml.label_id = l.id
      WHERE m.id = ?
      GROUP BY m.id;  
    `;
  • 在根目录.env新建全局变量APP_HOST并在app/config.js文件中导出(代码省略)
javascript
APP_HOST=http://localhost
APP_PORT=8000
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_DATABASE=zcjhub
MYSQL_USER=root
MYSQL_PASSWORD=54088qq
  • 修改controller/file.controller.js文件的saveAvatarInfo函数
javascript
/*-------------------controller/file.controller.js-----------------*/
const fileService = require('../service/file.service.js')
const userService = require('../service/user.service')
const {AVATAR_PATH} = require('../constants/file-path')
const { APP_HOST,APP_PORT } =  require('../app/config')
class FileController {
  async saveAvatarInfo(ctx, next) {
    // 1.获取图像相关信息
    const { mimetype, filename, size } = ctx.req.file
    const {id} = ctx.user

    // 2.将图像信息数据保存到数据库中
    const result = await fileService.createAvatar(id, mimetype, filename, size)

    // 3.将图片地址保存到user表中
    const avatarUrl = `${APP_HOST}:${APP_PORT}/users/${id}/avatar`;
    await userService.updateAvatarUrlById(avatarUrl, id)
    // 4.返回结果
    ctx.body = '上传头像成功~'
  }
}

module.exports = new FileController()
  • 在service/user.service.js文件中添加updateAvatarUrlById方法
javascript
/*------------service/user.service.js-----------------*/
const connection = require('../app/database')
class Userservice {
  async create(user) {
    const { name, password } = user
    const statement = `
      INSERT INTO users (name,password) VALUES (?, ?);
    `
    const result = await connection.execute(statement, [name, password])
    // 将user存储到数据库中
    return result
  }

  async getUserByName(name){
    const statement = `
      SELECT * FROM users WHERE name = ?;
    `
    const result = await connection.execute(statement,[name])
    return result[0]
  }

  async updateAvatarUrlById(avatarUrl, userId){
    const statement = `UPDATE users SET avatar_url = ? WHERE id = ?`
    const [result] = await connection.execute(statement,[avatarUrl, userId])
    return result
  }
}

module.exports = new Userservice()

第五节:动态配图-动态配图的上传处理

  • 在router/file.router.js添加上传配图路由
javascript
/*------------------router/file.router.js------------------*/
const Router = require("koa-router")

const { verifyAuth } = require('../middleware/auth.middleware')

const { avatarHandler, pictureHandler } = require('../middleware/file.middleware')

const { saveAvatarInfo, savePictureInfo } = require('../controller/file.controller.js')

const fileRouter = new Router({ prefix: '/upload' })

fileRouter.post('/avatar', verifyAuth, avatarHandler, saveAvatarInfo)
fileRouter.post('/picture', verifyAuth, pictureHandler, savePictureInfo)

module.exports = fileRouter
  • 在middleware/file.middleware.js文件中创建pictureHandler中间件函数
javascript
/*---------------middleware/file.middleware.js----------------*/
const Multer = require('koa-multer')

const { AVATAR_PATH, PICTURE_PATH } = require('../constants/file-path')

const avatarUpload = Multer({
  dest: AVATAR_PATH
})

const avatarHandler = avatarUpload.single('avatar')

const pictureUpload = Multer({
  dest: PICTURE_PATH
})

const pictureHandler = pictureUpload.array('picture', 9)

module.exports = {
  avatarHandler,
  pictureHandler
}
  • 在constants/file-path.js文件中创建常量PICTURE_PATH并导出
javascript
/*-------------constants/file-path.js---------------------*/
const AVATAR_PATH = './uploads/avatar'
const PICTURE_PATH = './uploads/picture'

module.exports = {
  AVATAR_PATH,
  PICTURE_PATH
}
  • 在controller/file.controller.js文件中创建savePictureInfo控制器函数保存图片信息
javascript
/*--------------controller/file.controller.js------------------------*/
const fileService = require('../service/file.service.js')
const userService = require('../service/user.service')
const { AVATAR_PATH } = require('../constants/file-path')
const { APP_HOST, APP_PORT } = require('../app/config')
class FileController {
  async saveAvatarInfo(ctx, next) {
    // 1.获取图像相关信息
    const { mimetype, filename, size } = ctx.req.file
    const { id } = ctx.user

    // 2.将图像信息数据保存到数据库中
    const result = await fileService.createAvatar(id, mimetype, filename, size)

    // 3.将图片地址保存到user表中
    const avatarUrl = `${APP_HOST}:${APP_PORT}/users/${id}/avatar`;
    await userService.updateAvatarUrlById(avatarUrl, id)
    // 4.返回结果
    ctx.body = '上传头像成功~'
  }

  async savePictureInfo(ctx, next) {
    // 1.获取图像信息
    const files = ctx.req.files
    const { id } = ctx.user
    const { momentId } = ctx.query
    // 2.将所有的文件信息保存到数据库中
    for (let file of files) {
      const { filename, mimetype, size } = file
      const result = await fileService.createFile(filename, mimetype, size, id, momentId)    
    }
    ctx.body = '动态配图上传完成~'
  }
}

module.exports = new FileController()
  • 创建file表保存上传的文件信息(一张或多张配图)
sql
CREATE TABLE IF NOT EXISTS file (
	id INT PRIMARY KEY AUTO_INCREMENT,
	filename VARCHAR(100) NOT NULL UNIQUE,
	mimetype VARCHAR(30),
	size INT,
	moment_id INT,
	user_id INT,
	createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
	updateAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
	FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,
	FOREIGN KEY(moment_id) REFERENCES moment(id) ON DELETE CASCADE ON UPDATE CASCADE
)
  • 在service/file.service.js文件中创建createFile函数
javascript
/*----------------service/file.service.js-----------------*/
const connection = require('../app/database')

class FileService {
  async createAvatar(userId, mimetype, filename, size) {
    const statement = `
      INSERT INTO avatar (user_id, filename, mimetype,size) VALUES(?,?,?,?)
    `
    const [result] = await connection.execute(statement, [userId, filename, mimetype, size])
    return result
  }
  async getAvatarByUserId(userId) {
    const statement = `SELECT * FROM avatar WHERE user_id = ?;`
    const [result] = await connection.execute(statement, [userId])
    return result[0]
  }
  async createFile(filename, mimetype, size, userId, momentId) {
    const statement = `
      INSERT INTO file (user_id, filename, mimetype,size, moment_id) VALUES(?,?,?,?,?);
    `
    const [result] = await connection.execute(statement, [userId, filename, mimetype, size, momentId])
    return result
  }
}

module.exports = new FileService()

第六节:动态配图-返回动态配图信息

  • 修改service/moment.service.js文件getMomentById函数和getMomentLis的sql,添加配图路径字段
sql
const statement = `
      SELECT 
        m.id id, m.content content, m.createAt createTime, m.updateAt updateTime,
        JSON_OBJECT('id', u.id, 'name', u.name, 'avatarUrl', u.avatar_url) author,
        IF(COUNT(l.id),JSON_ARRAYAGG(
          JSON_OBJECT('id', l.id, 'name', l.name)
        ),NULL) labels,
        (SELECT IF(COUNT(c.id),JSON_ARRAYAGG(
          JSON_OBJECT('id', c.id, 'content', c.content, 'commentId', c.comment_id, 'createTime', c.createAt,
                      'user', JSON_OBJECT('id', cu.id, 'name', cu.name, 'avatarUrl', cu.avatar_url))
        ),NULL) FROM comment c LEFT JOIN users cu ON c.user_id = cu.id WHERE m.id = c.moment_id) comments,
        (SELECT JSON_ARRAYAGG(CONCAT('${APP_HOST}:${APP_PORT}/moment/images/', file.filename)) FROM file WHERE m.id = file.moment_id) images
      FROM moment m
      LEFT JOIN users u ON m.user_id = u.id
      LEFT JOIN moment_label ml ON m.id = ml.moment_id
      LEFT JOIN label l ON ml.label_id = l.id
      WHERE m.id = ?
      GROUP BY m.id;  
    `;
    
    
   const statement = `
      SELECT 
        m.id id,
        m.content content,
        m.createAt createAt,
        m.updateAt updateAt,
        JSON_OBJECT('id',u.id,'name',u.name) author,
        (SELECT COUNT(*) FROM comment c WHERE c.moment_id = m.id) commentCount,
        (SELECT COUNT(*) FROM moment_label ml WHERE ml.moment_id = m.id) LabelCount,
        (SELECT JSON_ARRAYAGG(CONCAT('${APP_HOST}:${APP_PORT}/moment/images/', file.filename)) FROM file WHERE m.id = file.moment_id) images
      FROM moment m
      LEFT JOIN users u ON m.user_id = u.id
      LIMIT ?, ?;
    `
  • 在router/moment.router.js文件中添加查询配图接口路由
javascript
/*------------------router/moment.router.js------------------*/
const Router = require('koa-router')

const { verifyAuth, verifyPermission } = require('../middleware/auth.middleware')

const { verifyLabelExists } = require('../middleware/label.middleware')

const { create, detail, list, update, remove, addLabels, fileInfo } = require('../controller/moment.controller')

const momentRouter = new Router({ prefix: '/moment' })

momentRouter.post('/', verifyAuth, create)
momentRouter.get('/', list)
momentRouter.get('/:momentId', detail)
// 1.用户必须登录 2.用户具备权限
momentRouter.patch('/:momentId', verifyAuth, verifyPermission, update)
momentRouter.delete('/:momentId', verifyAuth, verifyPermission, remove)

momentRouter.post('/:momentId/labels', verifyAuth, verifyPermission, verifyLabelExists, addLabels)

// 动态配图的服务
momentRouter.get('/images/:filename', fileInfo)

module.exports = momentRouter
  • 在controller/moment.controller.js文件中添加fileInfo控制器函数
javascript
/*-------------------controller/moment.controller.js----------------*/
const fs = require('fs')
const momentService = require('../service/moment.service')
const fileService = require('../service/file.service')
const { PICTURE_PATH } = require('../constants/file-path')

class MomentController {
  async create(ctx, next) {
    // 1.获取数据(user_id, content)
    const userId = ctx.user.id
    const content = ctx.request.body.content
    // 2.将数据插入到数据库
    const result = await momentService.create(userId, content)
    ctx.body = result
  }
  async detail(ctx, next) {
    // 1.获取数据(momentId)
    const momentId = ctx.params.momentId
    // 2.根据id去查询这条数据
    const result = await momentService.getMomentById(momentId)
    ctx.body = result
  }
  async list(ctx, next) {
    // 1.获取数据
    const { offset, size } = ctx.query
    // 2.查询列表
    const result = await momentService.getMomentList(offset, size)
    ctx.body = result
  }
  async update(ctx, next) {
    // 1.获取参数
    const { momentId } = ctx.params
    const { content } = ctx.request.body

    // 2.修改内容
    const result = await momentService.update(content, momentId)
    ctx.body = '修改内容' + momentId + content
  }
  async remove(ctx, next) {
    // 1.获取参数
    const { momentId } = ctx.params
    // 2.删除动态
    const result = await momentService.remove(momentId)
    ctx.body = '删除动态成功'
  }
  async addLabels(ctx, next) {
    // 1.获取标签和动态id
    const { labels } = ctx
    const { momentId } = ctx.params

    // 2.添加所有的标签
    for (let label of labels) {
      // 2.1判断标签是否已经和动态有关系
      const isExists = await momentService.hasLabel(momentId, label.id)
      if (!isExists) {
        const result = await momentService.addLabel(momentId, label.id)
      }
    }
    ctx.body = '给动态添加标签成功~'
  }
  async fileInfo(ctx, next) {
    const { filename } = ctx.params
    const fileInfo = await fileService.getFileByFilename(filename)
    
    // 2.提供图像信息
    ctx.response.set('content-type', fileInfo.mimetype)
    ctx.body = fs.createReadStream(`${PICTURE_PATH}/${filename}`)
  }
}

module.exports = new MomentController()
  • 在service/file.service.js文件中创建getFileByFilename函数
javascript
/*-----------------service/file.service.js---------------------*/
const connection = require('../app/database')

class FileService {
  async createAvatar(userId, mimetype, filename, size) {
    const statement = `
      INSERT INTO avatar (user_id, filename, mimetype,size) VALUES(?,?,?,?)
    `
    const [result] = await connection.execute(statement, [userId, filename, mimetype, size])
    return result
  }
  async getAvatarByUserId(userId) {
    const statement = `SELECT * FROM avatar WHERE user_id = ?;`
    const [result] = await connection.execute(statement, [userId])
    return result[0]
  }
  async createFile(filename, mimetype, size, userId, momentId) {
    const statement = `
      INSERT INTO file (user_id, filename, mimetype,size, moment_id) VALUES(?,?,?,?,?);
    `
    const [result] = await connection.execute(statement, [userId, filename, mimetype, size, momentId])
    return result
  }
  async getFileByFilename(filename) {
    const statement = `SELECT * FROM file WHERE filename = ?;`
    const [result] = await connection.execute(statement, [filename])
    return result[0]
  }
}

module.exports = new FileService()

第七节:动态配图-对配图大小进行处理

  • 在router/file.router.js文件中添加图片大小处理中间件pictureResize
javascript
/*--------------------router/file.router.js---------------------*/
const Router = require("koa-router")

const { verifyAuth } = require('../middleware/auth.middleware')

const { avatarHandler, pictureHandler, pictureResize } = require('../middleware/file.middleware')

const { saveAvatarInfo, savePictureInfo } = require('../controller/file.controller.js')

const fileRouter = new Router({ prefix: '/upload' })

fileRouter.post('/avatar', verifyAuth, avatarHandler, saveAvatarInfo)
fileRouter.post('/picture', verifyAuth, pictureHandler, pictureResize, savePictureInfo)


module.exports = fileRouter
  • npm i
  • 在middleware/file.middleware.js文件中创建pictureResize中间件函数
javascript
/*-----------------------controller/moment.controller.js----------------*/
const path = require('path')
const Multer = require('koa-multer')
const Jimp = require('jimp')

const { AVATAR_PATH, PICTURE_PATH } = require('../constants/file-path')

const avatarUpload = Multer({
  dest: AVATAR_PATH
})

const avatarHandler = avatarUpload.single('avatar')

const pictureUpload = Multer({
  dest: PICTURE_PATH
})

const pictureHandler = pictureUpload.array('picture', 9)

const pictureResize = async (ctx, next) => {
  // 1.获取所有的图像信息
  const files = ctx.req.files
  // 2.对图像进行处理(sharp/jimp)
  for (let file of files) {
    console.log(file.destination, file.filename);
    const destPath = path.join(file.destination, file.filename)
    Jimp.read(file.path).then(image => {
      image.resize(1280, Jimp.AUTO).write(`${destPath}-large`)
      image.resize(640, Jimp.AUTO).write(`${destPath}-middle`)
      image.resize(320, Jimp.AUTO).write(`${destPath}-small`)
    })
  }

  await next()
}

module.exports = {
  avatarHandler,
  pictureHandler,
  pictureResize
}
  • fileInfo函数
javascript
/*----------------controller/moment.controller.js----------------*/
const fs = require('fs')
const momentService = require('../service/moment.service')
const fileService = require('../service/file.service')
const { PICTURE_PATH } = require('../constants/file-path')

class MomentController {
  async create(ctx, next) {
    // 1.获取数据(user_id, content)
    const userId = ctx.user.id
    const content = ctx.request.body.content
    // 2.将数据插入到数据库
    const result = await momentService.create(userId, content)
    ctx.body = result
  }
  async detail(ctx, next) {
    // 1.获取数据(momentId)
    const momentId = ctx.params.momentId
    // 2.根据id去查询这条数据
    const result = await momentService.getMomentById(momentId)
    ctx.body = result
  }
  async list(ctx, next) {
    // 1.获取数据
    const { offset, size } = ctx.query
    // 2.查询列表
    const result = await momentService.getMomentList(offset, size)
    ctx.body = result
  }
  async update(ctx, next) {
    // 1.获取参数
    const { momentId } = ctx.params
    const { content } = ctx.request.body

    // 2.修改内容
    const result = await momentService.update(content, momentId)
    ctx.body = '修改内容' + momentId + content
  }
  async remove(ctx, next) {
    // 1.获取参数
    const { momentId } = ctx.params
    // 2.删除动态
    const result = await momentService.remove(momentId)
    ctx.body = '删除动态成功'
  }
  async addLabels(ctx, next) {
    // 1.获取标签和动态id
    const { labels } = ctx
    const { momentId } = ctx.params

    // 2.添加所有的标签
    for (let label of labels) {
      // 2.1判断标签是否已经和动态有关系
      const isExists = await momentService.hasLabel(momentId, label.id)
      if (!isExists) {
        const result = await momentService.addLabel(momentId, label.id)
      }
    }
    ctx.body = '给动态添加标签成功~'
  }
  async fileInfo(ctx, next) {
    let { filename } = ctx.params
    const fileInfo = await fileService.getFileByFilename(filename)
    const { type } = ctx.query
    const types = ['small', 'middle', 'large']
    if (types.some(item => item === type)) {
      filename = filename + '-' + type
      console.log(filename);
    }

    // 2.提供图像信息
    ctx.response.set('content-type', fileInfo.mimetype)
    ctx.body = fs.createReadStream(`${PICTURE_PATH}/${filename}`)
  }
}

module.exports = new MomentController()