动手学koa2 (后端接口实现)

搬运自www.bilibili.com/video/BV13A...

一. 项目的初始化

1 npm 初始化

csharp 复制代码
npm init -y

生成package.json文件:

  • 记录项目的依赖

2 git 初始化

csharp 复制代码
git init

生成'.git'隐藏文件夹, git 的本地仓库

3 创建 ReadMe 文件

二. 搭建项目

1 安装 Koa 框架

npm install koa

2 编写最基本的 app

创建src/main.js

js 复制代码
const Koa = require('koa')

const app = new Koa()

app.use((ctx, next) => {
  ctx.body = 'hello world'
})

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

3 测试

在终端, 使用node src/main.js

三. 项目的基本优化

1 自动重启服务

安装 nodemon 工具

css 复制代码
npm i nodemon -D

编写package.json脚本

json 复制代码
"scripts": {
  "dev": "nodemon ./src/main.js",
  "test": "echo \"Error: no test specified\" && exit 1"
},

执行npm run dev启动服务

2 读取配置文件

安装dotenv, 读取根目录中的.env文件, 将配置写到process.env

css 复制代码
npm i dotenv

创建.env文件

ini 复制代码
APP_PORT=8000

创建src/config/config.default.js

js 复制代码
const dotenv = require('dotenv')

dotenv.config()

// console.log(process.env.APP_PORT)

module.exports = process.env

改写main.js

js 复制代码
const Koa = require('koa')

const { APP_PORT } = require('./config/config.default').APP_PORT

const app = new Koa()

app.use((ctx, next) => {
  ctx.body = 'hello api'
})

app.listen(APP_PORT, () => {
  console.log(`server is running on http://localhost:${APP_PORT}`)
})

四. 添加路由

路由: 根据不同的 URL, 调用对应处理函数

1 安装 koa-router

css 复制代码
npm i koa-router

步骤:

  1. 导入包
  2. 实例化对象
  3. 编写路由
  4. 注册中间件

2 编写路由

创建src/router目录, 编写user.route.js

js 复制代码
const Router = require('koa-router')

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

// GET /users/
router.get('/', (ctx, next) => {
  ctx.body = 'hello users'
})

module.exports = router

3 改写 main.js

js 复制代码
const Koa = require('koa')

const { APP_PORT } = require('./config/config.default')

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

const app = new Koa()

app.use(userRouter.routes())

app.listen(APP_PORT, () => {
  console.log(`server is running on http://localhost:${APP_PORT}`)
})

五. 目录结构优化

1 将 http 服务和 app 业务拆分

创建src/app/index.js

js 复制代码
const Koa = require('koa')

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

const app = new Koa()

app.use(userRouter.routes())

module.exports = app

改写main.js

js 复制代码
const { APP_PORT } = require('./config/config.default')

const app = require('./app')

app.listen(APP_PORT, () => {
  console.log(`server is running on http://localhost:${APP_PORT}`)
})

2 将路由和控制器拆分

路由: 解析 URL, 分布给控制器对应的方法

控制器: 处理不同的业务

改写user.route.js

js 复制代码
const Router = require('koa-router')

const { register, login } = require('../controller/user.controller')

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

// 注册接口
router.post('/register', register)

// 登录接口
router.post('/login', login)

module.exports = router

创建controller/user.controller.js

js 复制代码
class UserController {
  async register(ctx, next) {
    ctx.body = '用户注册成功'
  }

  async login(ctx, next) {
    ctx.body = '登录成功'
  }
}

module.exports = new UserController()

六. 解析 body

1 安装 koa-body

css 复制代码
npm i koa-body

2 注册中间件

改写app/index.js

js 复制代码
const { koaBody } = require('koa-body');

3 解析请求数据

改写user.controller.js文件

js 复制代码
const { createUser } = require('../service/user.service')

class UserController {
  async register(ctx, next) {
    // 1. 获取数据
    // console.log(ctx.request.body)
    const { user_name, password } = ctx.request.body
    // 2. 操作数据库
    const res = await createUser(user_name, password)
    // console.log(res)
    // 3. 返回结果
    ctx.body = ctx.request.body
  }

  async login(ctx, next) {
    ctx.body = '登录成功'
  }
}

module.exports = new UserController()

4 拆分 service 层

service 层主要是做数据库处理

创建src/service/user.service.js

js 复制代码
class UserService {
  async createUser(user_name, password) {
    // todo: 写入数据库
    return '写入数据库成功'
  }
}

module.exports = new UserService()

七. 集成 sequlize

sequelize ORM 数据库工具

ORM: 对象关系映射

  • 数据表映射(对应)一个类
  • 数据表中的数据行(记录)对应一个对象
  • 数据表字段对应对象的属性
  • 数据表的操作对应对象的方法

1 安装 sequelize

css 复制代码
npm i mysql2 sequelize

2 连接数据库

src/db/seq.js

js 复制代码
const { Sequelize } = require('sequelize')

const {
  MYSQL_HOST,
  MYSQL_PORT,
  MYSQL_USER,
  MYSQL_PWD,
  MYSQL_DB,
} = require('../config/config.default')

const seq = new Sequelize(MYSQL_DB, MYSQL_USER, MYSQL_PWD, {
  host: MYSQL_HOST,
  dialect: 'mysql',
})

seq
  .authenticate()
  .then(() => {
    console.log('数据库连接成功')
  })
  .catch((err) => {
    console.log('数据库连接失败', err)
  })

module.exports = seq

3 编写配置文件

ini 复制代码
APP_PORT = 8000

MYSQL_HOST = localhost
MYSQL_PORT = 3306
MYSQL_USER = root
MYSQL_PWD = 123456
MYSQL_DB = zdsc

八. 创建 User 模型

1 拆分 Model 层

sequelize 主要通过 Model 对应数据表

创建src/model/user.model.js

js 复制代码
const { DataTypes } = require('sequelize')

const seq = require('../db/seq')

// 创建模型(Model zd_user -> 表 zd_users)
const User = seq.define('zd_user', {
  // id 会被sequelize自动创建, 管理
  user_name: {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true,
    comment: '用户名, 唯一',
  },
  password: {
    type: DataTypes.CHAR(64),
    allowNull: false,
    comment: '密码',
  },
  is_admin: {
    type: DataTypes.BOOLEAN,
    allowNull: false,
    defaultValue: 0,
    comment: '是否为管理员, 0: 不是管理员(默认); 1: 是管理员',
  },
})

// 强制同步数据库(创建数据表)
// User.sync({ force: true })

module.exports = User

九. 添加用户入库

所有数据库的操作都在 Service 层完成, Service 调用 Model 完成数据库操作

改写src/service/user.service.js

js 复制代码
const User = require('../model/use.model')

class UserService {
  async createUser(user_name, password) {
    // 插入数据
    // User.create({
    //   // 表的字段
    //   user_name: user_name,
    //   password: password
    // })

    // await表达式: promise对象的值
    const res = await User.create({ user_name, password })
    // console.log(res)

    return res.dataValues
  }
}

module.exports = new UserService()

同时, 改写user.controller.js

js 复制代码
const { createUser } = require('../service/user.service')

class UserController {
  async register(ctx, next) {
    // 1. 获取数据
    // console.log(ctx.request.body)
    const { user_name, password } = ctx.request.body
    // 2. 操作数据库
    const res = await createUser(user_name, password)
    // console.log(res)
    // 3. 返回结果
    ctx.body = {
      code: 0,
      message: '用户注册成功',
      result: {
        id: res.id,
        user_name: res.user_name,
      },
    }
  }

  async login(ctx, next) {
    ctx.body = '登录成功'
  }
}

module.exports = new UserController()

十. 错误处理

在控制器中, 对不同的错误进行处理, 返回不同的提示错误提示, 提高代码质量

js 复制代码
const { createUser, getUerInfo } = require('../service/user.service')

class UserController {
  async register(ctx, next) {
    // 1. 获取数据
    // console.log(ctx.request.body)
    const { user_name, password } = ctx.request.body

    // 合法性
    if (!user_name || !password) {
      console.error('用户名或密码为空', ctx.request.body)
      ctx.status = 400
      ctx.body = {
        code: '10001',
        message: '用户名或密码为空',
        result: '',
      }
      return
    }
    // 合理性
    if (getUerInfo({ user_name })) {
      ctx.status = 409
      ctx.body = {
        code: '10002',
        message: '用户已经存在',
        result: '',
      }
      return
    }
    // 2. 操作数据库
    const res = await createUser(user_name, password)
    // console.log(res)
    // 3. 返回结果
    ctx.body = {
      code: 0,
      message: '用户注册成功',
      result: {
        id: res.id,
        user_name: res.user_name,
      },
    }
  }

  async login(ctx, next) {
    ctx.body = '登录成功'
  }
}

module.exports = new UserController()

在 service 中封装函数

js 复制代码
const User = require('../model/use.model')

class UserService {
  async createUser(user_name, password) {
    // 插入数据
    // await表达式: promise对象的值
    const res = await User.create({ user_name, password })
    // console.log(res)

    return res.dataValues
  }

  async getUerInfo({ id, user_name, password, is_admin }) {
    const whereOpt = {}

    id && Object.assign(whereOpt, { id })
    user_name && Object.assign(whereOpt, { user_name })
    password && Object.assign(whereOpt, { password })
    is_admin && Object.assign(whereOpt, { is_admin })

    const res = await User.findOne({
      attributes: ['id', 'user_name', 'password', 'is_admin'],
      where: whereOpt,
    })

    return res ? res.dataValues : null
  }
}

module.exports = new UserService()

十一. 拆分中间件

为了使代码的逻辑更加清晰, 我们可以拆分一个中间件层, 封装多个中间件函数

1 拆分中间件

添加src/middleware/user.middleware.js

js 复制代码
const { getUerInfo } = require('../service/user.service')
const { userFormateError, userAlreadyExited } = require('../constant/err.type')

const userValidator = async (ctx, next) => {
  const { user_name, password } = ctx.request.body
  // 合法性
  if (!user_name || !password) {
    console.error('用户名或密码为空', ctx.request.body)
    ctx.app.emit('error', userFormateError, ctx)
    return
  }

  await next()
}

const verifyUser = async (ctx, next) => {
  const { user_name } = ctx.request.body

  if (getUerInfo({ user_name })) {
    ctx.app.emit('error', userAlreadyExited, ctx)
    return
  }

  await next()
}

module.exports = {
  userValidator,
  verifyUser,
}

2 统一错误处理

  • 在出错的地方使用ctx.app.emit提交错误
  • 在 app 中通过app.on监听

编写统一的错误定义文件

js 复制代码
module.exports = {
  userFormateError: {
    code: '10001',
    message: '用户名或密码为空',
    result: '',
  },
  userAlreadyExited: {
    code: '10002',
    message: '用户已经存在',
    result: '',
  },
}

3 错误处理函数

js 复制代码
module.exports = (err, ctx) => {
  let status = 500
  switch (err.code) {
    case '10001':
      status = 400
      break
    case '10002':
      status = 409
      break
    default:
      status = 500
  }
  ctx.status = status
  ctx.body = err
}

改写app/index.js

js 复制代码
const errHandler = require('./errHandler')
// 统一的错误处理
app.on('error', errHandler)

十二. 加密

在将密码保存到数据库之前, 要对密码进行加密处理

123123abc (加盐) 加盐加密

1 安装 bcryptjs

css 复制代码
npm i bcryptjs

2 编写加密中间件

js 复制代码
const crpytPassword = async (ctx, next) => {
  const { password } = ctx.request.body

  const salt = bcrypt.genSaltSync(10)
  // hash保存的是 密文
  const hash = bcrypt.hashSync(password, salt)

  ctx.request.body.password = hash

  await next()
}

3 在 router 中使用

改写user.router.js

js 复制代码
const Router = require('koa-router')

const {
  userValidator,
  verifyUser,
  crpytPassword,
} = require('../middleware/user.middleware')
const { register, login } = require('../controller/user.controller')

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

// 注册接口
router.post('/register', userValidator, verifyUser, crpytPassword, register)

// 登录接口
router.post('/login', login)

module.exports = router

十三. 登录验证

流程:

  • 验证格式
  • 验证用户是否存在
  • 验证密码是否匹配

改写src/middleware/user.middleware.js

js 复制代码
const bcrypt = require('bcryptjs')

const { getUerInfo } = require('../service/user.service')
const {
  userFormateError,
  userAlreadyExited,
  userRegisterError,
  userDoesNotExist,
  userLoginError,
  invalidPassword,
} = require('../constant/err.type')

const userValidator = async (ctx, next) => {
  const { user_name, password } = ctx.request.body
  // 合法性
  if (!user_name || !password) {
    console.error('用户名或密码为空', ctx.request.body)
    ctx.app.emit('error', userFormateError, ctx)
    return
  }

  await next()
}

const verifyUser = async (ctx, next) => {
  const { user_name } = ctx.request.body

  // if (await getUerInfo({ user_name })) {
  //   ctx.app.emit('error', userAlreadyExited, ctx)
  //   return
  // }
  try {
    const res = await getUerInfo({ user_name })

    if (res) {
      console.error('用户名已经存在', { user_name })
      ctx.app.emit('error', userAlreadyExited, ctx)
      return
    }
  } catch (err) {
    console.error('获取用户信息错误', err)
    ctx.app.emit('error', userRegisterError, ctx)
    return
  }

  await next()
}

const crpytPassword = async (ctx, next) => {
  const { password } = ctx.request.body

  const salt = bcrypt.genSaltSync(10)
  // hash保存的是 密文
  const hash = bcrypt.hashSync(password, salt)

  ctx.request.body.password = hash

  await next()
}

const verifyLogin = async (ctx, next) => {
  // 1. 判断用户是否存在(不存在:报错)
  const { user_name, password } = ctx.request.body

  try {
    const res = await getUerInfo({ user_name })

    if (!res) {
      console.error('用户名不存在', { user_name })
      ctx.app.emit('error', userDoesNotExist, ctx)
      return
    }

    // 2. 密码是否匹配(不匹配: 报错)
    if (!bcrypt.compareSync(password, res.password)) {
      ctx.app.emit('error', invalidPassword, ctx)
      return
    }
  } catch (err) {
    console.error(err)
    return ctx.app.emit('error', userLoginError, ctx)
  }

  await next()
}

module.exports = {
  userValidator,
  verifyUser,
  crpytPassword,
  verifyLogin,
}

定义错误类型

js 复制代码
module.exports = {
  userFormateError: {
    code: '10001',
    message: '用户名或密码为空',
    result: '',
  },
  userAlreadyExited: {
    code: '10002',
    message: '用户已经存在',
    result: '',
  },
  userRegisterError: {
    code: '10003',
    message: '用户注册错误',
    result: '',
  },
  userDoesNotExist: {
    code: '10004',
    message: '用户不存在',
    result: '',
  },
  userLoginError: {
    code: '10005',
    message: '用户登录失败',
    result: '',
  },
  invalidPassword: {
    code: '10006',
    message: '密码不匹配',
    result: '',
  },
}

改写路由

js 复制代码
// 登录接口
router.post('/login', userValidator, verifyLogin, login)

十四. 用户的认证

登录成功后, 给用户颁发一个令牌 token, 用户在以后的每一次请求中携带这个令牌.

jwt: jsonwebtoken

  • header: 头部
  • payload: 载荷
  • signature: 签名

1 颁发 token

1) 安装 jsonwebtoken

css 复制代码
npm i jsonwebtoken

2) 在控制器中改写 login 方法

js 复制代码
async login(ctx, next) {
  const { user_name } = ctx.request.body

  // 1. 获取用户信息(在token的payload中, 记录id, user_name, is_admin)
  try {
    // 从返回结果对象中剔除password属性, 将剩下的属性放到res对象
    const { password, ...res } = await getUerInfo({ user_name })

    ctx.body = {
      code: 0,
      message: '用户登录成功',
      result: {
        token: jwt.sign(res, JWT_SECRET, { expiresIn: '1d' }),
      },
    }
  } catch (err) {
    console.error('用户登录失败', err)
  }
}

3) 定义私钥

.env定义

ini 复制代码
JWT_SECRET = xzd

2 用户认证

1) 创建 auth 中间件

js 复制代码
const jwt = require('jsonwebtoken')

const { JWT_SECRET } = require('../config/config.default')

const { tokenExpiredError, invalidToken } = require('../constant/err.type')

const auth = async (ctx, next) => {
  const { authorization } = ctx.request.header
  const token = authorization.replace('Bearer ', '')
  console.log(token)

  try {
    // user中包含了payload的信息(id, user_name, is_admin)
    const user = jwt.verify(token, JWT_SECRET)
    ctx.state.user = user
  } catch (err) {
    switch (err.name) {
      case 'TokenExpiredError':
        console.error('token已过期', err)
        return ctx.app.emit('error', tokenExpiredError, ctx)
      case 'JsonWebTokenError':
        console.error('无效的token', err)
        return ctx.app.emit('error', invalidToken, ctx)
    }
  }

  await next()
}

module.exports = {
  auth,
}

2) 改写 router

js 复制代码
// 修改密码接口
router.patch('/', auth, (ctx, next) => {
  
})

修改密码

改写router 里面中间件

js 复制代码
// 在 control 里面 引入
const {register,login,changePassword} =require('../controller/user.controller')
// 中间件排列
router.patch('/',auth,cryptPassword,changePassword)

user.control 里面

js 复制代码
const {createUser,getUserInfo,updateUserInfo} =require('../service/user.service')  


// 修改密码
   async changePassword(ctx,next){
     // 获取修改信息
     const id=ctx.state.user.id
     const password=ctx.request.body.password
     console.log(id,password)
     // 操作数据库
     if(await updateUserInfo({id,password})){
        ctx.body ={
            code:200,
            message:'密码修改成功',
            result:''
        }
     }else{
        ctx.body={
            code:'1007',
            message:'密码修改失败',
            result:''
        }
     }
    }

user.service 里面

js 复制代码
  async updateUserInfo({ id, user_name, password, is_admin }) {
        const whereOpt = {id}
        const newUser ={}

        user_name && Object.assign(newUser, { user_name })
        password && Object.assign(newUser, { password })
        is_admin && Object.assign(newUser, { is_admin })

        const res = await User.update(newUser, {
            where: whereOpt,
        }
        );
    
        return res[0]>0  ? true : false;
    }

十五.商品模块

图片上传模块

1) 创建一个goods.router.js

2)用index.js 统一管理 router

goods.router.js

js 复制代码
const Router = require('koa-router');
const router = new Router({prefix:'/goods'});
const {upload} =require('../controller/goods.controller');

router.patch('/upload',auth,cryptPassword,changePassword)

module.exports = router;

index.js 统一 管理router

js 复制代码
const  fs =require('fs')

const  Router =require('koa-router')
const  router =new Router()

fs.readdirSync(__dirname).forEach(file=>{
    if(file !== 'index.js'){
        let r=require('./'+file)
        router.use(r.routes())
    }
})

module.exports=router

3) 在app index里面引入

js 复制代码
const Koa =require('koa');
const{ koaBody }=require('koa-body');
const router= require('../router/index');

const errHandler = require('./errHandler');

//导入errHeadler


// 实例化router
const app =new Koa();


//创建中间件
app.use(koaBody()); // 在所有的中件之前注册
app.use(router.routes())
.use(router.allowedMethods());



// 导出app对象
app.on('error',errHandler);
module.exports=app;
  1. 用 control 管理返回

    js 复制代码
    class GoodsControl{
        async upload(ctx,next){
            ctx.body = {
                code:200,
                message:'图片上传成功',
        }
    }
    }
    module.exports =  new GoodsControl()

5.导入验证参数的包,和koa-body

js 复制代码
npm i koa-parameter
npm i koa-body

index 引用

js 复制代码
app.use(koaBody({
    multipart:true,
    formidable:{
        // 这里不可以写相对地址,用绝对路径进行调用
        uploadDir:path.join(__dirname,'../uploads'),
        keepExtensions:true
    }
})); 
app.use(parameter(app));

商品信息上传

goods.router.js

js 复制代码
// 引入中间件
const {validator} =require('../middleware/goods.middleware');
// 发布商品接口
router.post('/publish',auth,authAdmin,validator,create)

1 引入中间件 goods.middle 验证数据 结构

js 复制代码
const  {goodsFormatError}= require('../constant/err.type')
const validator =async (ctx,next)=>{
 try{
    ctx.verifyParams({
        goods_name:{
            type:'string',
            required:true
        },
        goods_price:{
            type:'number',
            required:true
        },
        goods_num:{
            type:'number',
            required:true
        },
        goods_img:{
            type:'string',
            required:true
        }
    })
    
 }catch(err){
     console.error(err)
     goodsFormatError.result=err
     return ctx.app.emit('error',goodsFormatError,ctx)
 }
  await next()
}
module.exports = {validator}

2.create 控制函数的定义

js 复制代码
// 定义createGoods的函数
const {createGoods}= require('../service/goods.service')


async create(ctx,next){
        // 直接调用service的createGoods
        try{
          const res=  await createGoods(ctx.request.body)
          ctx.body={
            code:'200',
            message:'商品创建成功',
            result:res
          }
        }catch(err){
           console.log(err);
           return ctx.app.emit('error',publishGoodsError,ctx)
        }
      
    }

3建立一个goods.service

js 复制代码
class GoodService{
    async createGoods(goods){   
     const res=await Goods.create(goods)
     console.log(res)
     return res.dataValues
    }
}
 module.exports = new GoodService();
js 复制代码
const {auth,authAdmin} =require('../middleware/auth.middleware');

auth.middleware.js

JS 复制代码
const jwt = require('jsonwebtoken')
const {JWT_SECRET}=require('../config/config.default')
const {tokenExpiredError, invalidToken,hasNotAdminPermission}=require('../constant/err.type')

const auth = async (ctx,next)=> {
    const {authorization={}}=ctx.request.header
    const token =authorization.replace('Bearer ','')
    try{
        const user =jwt.verify(token,JWT_SECRET)
        // 解密用户信息
        ctx.state.user = user
        
        
    }catch(err){
        switch (err.name) {
            case 'TokenExpiredError':
              console.error('token已过期', err)
              return ctx.app.emit('error', tokenExpiredError, ctx)
            case 'JsonWebTokenError':
              console.error('无效的token', err)
              return ctx.app.emit('error', invalidToken, ctx)
          }
    }
   await next()
}

const authAdmin = async (ctx,next)=> {
    const is_admin=ctx.state.user.is_admin
 
    if(!is_admin){
        console.error('非管理员禁止访问',ctx.state.user)
        return ctx.app.emit('error', hasNotAdminPermission, ctx)
    }

    await next() 
}

module.exports={
    auth,
    authAdmin 
}

商品信息更新

js 复制代码
// 更新商品接口
router.put('/:id',auth,authAdmin,validator,update)

goods.controller.js

js 复制代码
 async update(ctx,next){
       try{
        const res= await updateGoods(ctx.params.id,ctx.request.body)
        if(res){
            ctx.body={
                code:'200',
                message:'商品修改成功',
                result:''
              } 
        }
        else{
            return ctx.app.emit('error',invalidIdError,ctx)
        }
       }catch(err){
         console.error(err)
         return ctx.app.emit('error',updateGoodsError,ctx)
       }
    }

goods.service.js

js 复制代码
  async updateGoods(id,goods){
        const res =await Goods.update(goods,{where:{id}})
        return res[0]>0  ? true : false;
    }

商品下架与重新上架(软删除)

重新修改商品model goods.model.js

js 复制代码
const {DataTypes}=require('sequelize');

const seq= require('../db/seq')

const Goods=seq.define('goods',{
    goods_name:{
        type:DataTypes.STRING(100),
        allowNull:false,
        comment:'商品名称'
    },
    goods_price:{
        type:DataTypes.DECIMAL(10,2),
        allowNull:false,
        comment:'商品价格'
    },
    goods_num:{
        type:DataTypes.INTEGER,
        allowNull:false,
        comment:'商品数量'
    },
    goods_img:{
     type:DataTypes.STRING(255),
     allowNull:false,
     comment:'商品图片'
    },
   
},{
    paranoid:true,   // 关键
})
// Goods.sync({force:true})

module.exports=Goods

goods.router.js

JS 复制代码
//下架商品
router.post('/:id/off',auth,authAdmin,remove)
//从新上架
router.post('/:id/on',auth,authAdmin,restore)

goods.controller.js

JS 复制代码
  async remove(ctx,next){
       const res= await removeGoods(ctx.params.id,ctx)
       console.log(res);
       if(res){
        ctx.body={
            code:200,
            message:'下架成功',
            result:''
        }
       }else{
        return ctx.app.emit('error',invalidIdError,ctx)
       }
        
    }
    async restore(ctx,next){
        const res= await restoreGoods(ctx.params.id,ctx)
        if(res){
         ctx.body={
             code:200,
             message:'重新上架成功',
             result:''
         }
        }else{
         return ctx.app.emit('error',invalidIdError,ctx)
        }
         
     }

goods.service.js

js 复制代码
 async removeGoods(id){
        const res= await Goods.destroy({where:{id}});

        return res>0?true:false;
    }
    async restoreGoods(id){
        const res= await Goods.restore({where:{id}});
  
        return res>0?true:false;
    }

商品列表

goods.router.js

JS 复制代码
// 获取商品的列表
router.get('/',findAll)

goods.controller.js

js 复制代码
  async findAll(ctx){
      const {pageNum=1,pageSize=10}=ctx.request.query
      // 处理返回的数据
     const res= await findGoods(pageNum,pageSize)
      // 返回结果
      ctx.body={
        code:200,
        message:'查询商品列表成功',
        result:res,
      }
    } 

goods.service.js

JS 复制代码
 async findGoods(pageNum,pageSize){
        const count= await Goods.count();
     
       const offset=pageSize*(pageNum-1);
       const rows= await Goods.findAll({offset:offset,limit:pageSize*1})
       return {pageNum,
               pageSize,
               total:count,
               list:rows,
            }
    }

十六、购物车模块

添加到购物车的商品

cart.router.js

JS 复制代码
const Router= require('koa-router');
const {auth} =require('../middleware/auth.middleware');
const {cartValidator}= require('../middleware/cart.middleware')
const {addtoCart}=require('../controller/cart.controller')
// 实例化router
const router= new Router({prefix:'/cart'});
// 设置router
router.post('/',auth,cartValidator,addtoCart)

cart.middle.js

js 复制代码
const {invalidIdError} = require('../constant/err.type')
// 验证一购物车信息
const cartValidator =async(ctx,next)=>{
  try{
    ctx.verifyParams({
       goods_id:'number',
    })
  }catch(err){
      console.error(err);
    //   invalidIdError.result=err;
      return ctx.app.emit('error',invalidIdError,ctx);
  }
  await next();
}
module.exports = {cartValidator};

cart.controller.js

JS 复制代码
const {addGoodstoCart}=require('../service/cart.service')
const {addtoCartError}=require('../constant/err.type')
class CartController{
    // 添加商品到购物车
    async addtoCart(ctx,next){
        // 提取变量user_id 和 goods_id
         const user_id= ctx.state.user.id;
         const {goods_id,goods_num}= ctx.request.body;
        
         try{

            const res = await addGoodstoCart(user_id,goods_id,goods_num);
            console.log(res)
            ctx.body= {
                code:200,
                message:'添加商品成功',
                result:res
            }
         }catch(err){
             console.err(err);
             ctx.app.emit('error',addtoCartError,ctx)
         }
    }
}

module.exports = new CartController();

cart.service.js

JS 复制代码
const Cart = require('../model/cart.model')
class CartService{
 async addGoodstoCart(user_id,goods_id,goods_num){
        //先进行查找
  let  hascart =await Cart.findOne({
       where:{
        goods_id,
        user_id,
       }
  })
  
  if(hascart){
    await Cart.update({
        goods_num
   },{where:{ goods_id,
        user_id,}})
     return {
        goods_id,
        user_id,
        goods_num
     }
  }else{
    let res = await Cart.create({
        goods_id,
        user_id,
        goods_num
    })
        return res.dataValues
  }
   
}
}
module.exports = new CartService();

获得购物车列表

cart.router.js

js 复制代码
// 购物车列表
router.get('/',auth,findAll)

cart.controller.js

js 复制代码
  async findAll(ctx,next){
     // 解析请求结果
     const{pageNum=1,pageSize=10}=ctx.request.query;
     // 操作数据库
     const res =await findCarts(pageNum,pageSize);
     ctx.body= {
        code:200,
        message:'查询成功',
        result:res
     }
   }

cart.service.js

这里使用goods_id 来查找 goods 表格里的商品 外键设置

js 复制代码
const Goods = require('../model/goods.model')


async  findCarts(pageNum,pageSize){
   console.log('测试a');
  const{count,rows} = await Cart.findAndCountAll({
      attributes:['id','goods_num','selected'],
      limit:pageSize*1,
      offset:(pageNum-1)*pageSize,
      order:[['id','desc']],
      include:{model:Goods,
        as:'goods_info',
        attributes:['id','goods_name','goods_price','goods_img']
      },
  })

这里在cart.model 里面设置

js 复制代码
Cart.belongsTo(Goods,{foreignKey:'goods_id',
targetKey:'id', 
as: 'goods_info'})
module.exports=Cart

购物车更新

这里的参数设置中间件发生了变化,使用闭包函数规定

cart.middle.js

js 复制代码
const validator =(rules)=>{ 

   return async(ctx,next)=>{
    try{
      ctx.verifyParams(
         rules
      )
    }catch(err){
        console.error(err);
        cartFormatError.result=err;
      //   invalidIdError.result=err;
        return ctx.app.emit('error', cartFormatError,ctx);
    }
    await next();
}
}

cart.router.js 通过传入rules 的规则

js 复制代码
// 更新购物车,使用patch
router.patch('/:id',auth,validator({goods_num:{type:'number',required:false},
selected:{type:'bool',required:false}}),updateCart)

cart.controller.js

JS 复制代码
// 更新数据库
 async updateCart(ctx,next){
        // 提取变量user_id 和 goods_id
        const id=ctx.params.id;
        const {goods_num,selected} =ctx.request.body;
        // 未提交参数错误
        try{
            if(goods_num===undefined&&selected===undefined){
                cartFormatError.message='num和selected参数错误不能同时为空'
                return ctx.app.emit('error', cartFormatError,ctx)
            }
            // 操作数据库
            const res =await updateCartMsg({id,goods_num,selected})
            ctx.body={
                code:200,
                message:'更新成功',
                result:res
            }
        }catch(err){
              console.error(err);
              ctx.app.emit('error',updateCartError,ctx)
        }
    }

cart.service.js

js 复制代码
// 更新数据库
  async updateCartMsg(params){
    const {id,goods_num,selected}= params;
    const res =await Cart.findByPk(id);
    if(!res)
     return ''

    goods_num !==undefined ? (res.goods_num = goods_num):''
    if(selected !==undefined){
      res.selected =selected;
    }
    // 更新结果
   return await  res.save()
  }

购物车删除商品

cart.router.js

js 复制代码
// 删除购物车
router.delete('/',auth,validator({ ids:'array'}),remove)

cart.controller.js

JS 复制代码
   async remove(ctx,next){
        try{
            const ids= ctx.request.body.ids;
            const res =await removeCart(ids);
            if(res>0){
                ctx.body={
                    code:200,
                    message:'删除购物车成功',
                    result:res
                }
            }else{
                ctx.body={
                    code:500,
                    message:'购物车删除失败',
                    result:0
                }
            }
            
        }catch(err){
            console.error(err);
            removeCartError.result;
            ctx.app.emit('error',removeCartError,ctx)
        }
    }

cart.service.js

这里使用的是硬删除

js 复制代码
// 删除购物车
async removeCart(ids){
 return await Cart.destroy({
    where:{
      id:{
      [Op.in]: ids,
    },
  }
  })
}

商品全选

​ cart.router.js

JS 复制代码
// 选择全部
router.post('/selectAll',auth,selectall)

cart.controller.js

js 复制代码
  async selectall(ctx,next){
        const  user_id= ctx.state.user.id;
        const  selected= ctx.request.body.selected;
    
        const res=await selectAllGoods(user_id,selected);
        ctx.body={
            code:200,
            message:'全选成功',
            result:res
        }
    }

cart.service.js

JS 复制代码
//全选购物车商品
async selectAllGoods(user_id,selected){
   console.log('测试:',user_id,selected);
    return Cart.update({selected},{
        where:{user_id}
    })
}

十七、订单模块

创建订单

创建 order.model.js

js 复制代码
const { DataTypes } = require('sequelize')
const seq =require('../db/seq')

const Order= seq.define('demo_order',{
    user_id:{type:DataTypes.INTEGER,
        allowNull:false,
        comment:'用户id'
    },
   address_id:{
        type:DataTypes.STRING,
        comment:'地址id'
    },
    goods_info:{
        type:DataTypes.STRING,
        allowNull:false,
        comment:'商品信息'
    },
   total:{
        type:DataTypes.DECIMAL,
        allowNull:false,
        comment:'总金额'
    },
    order_number:{
        type:DataTypes.CHAR(30),
        allowNull:false,
        comment:'订单号'
    },
    status:{
        type:DataTypes.TINYINT,
        allowNull:false,
        defaultValue:0,
        comment:'订单状态'
    }
})
// Order.sync({force:true})

module.exports=Order

order.router.js

JS 复制代码
// 创建订单
router.post('/',auth,validator({
    address_id:'int',
    goods_info:'string',
    total:"string",
    status:'int'
}),create)

order.middleware.js

js 复制代码
// 获取订单列表
router.get('/',auth,getList)

order.controller.js

JS 复制代码
   async create(ctx){
    const params= ctx.request.body;
    const user_id = ctx.state.user.id;
    const order_number= 'kiri'+Date.now();
    try{
      const res = await createOrder({user_id,order_number,...params})
      ctx.body={
         code:200,
         message:'创建订单成功',
         data:res
      }
    }catch(err){
      console.error(err);
      createOrderError.message=err;
      ctx.app.emit('error',createOrderError,ctx);
    }
}

order.service.js

js 复制代码
    async createOrder(order){
     const {user_id,address_id,goods_info,total,status,order_number}=order
     const res = Order.create({user_id,address_id,goods_info,total,status,order_number})
     return res ? 'success' : 'fail';
  }

获取列表

order.router.js

JS 复制代码
// 获取订单列表
router.get('/',auth,getList)

order.controller.js

JS 复制代码
  async getList(ctx){
    const user_id = ctx.state.user.id;
    const {PageNum=1,PageSize=10,status=0}=ctx.request.query;
    try{
      const res = await getOrderList({user_id,PageNum,PageSize,status})
      ctx.body={
         code:200,
         message:'获取订单列表成功',
         data:res
      }
    }catch(err){
      console.error(err);
      ctx.app.emit('error',getOrderListError,ctx);
    }
  }

order.service.js

js 复制代码
  async getOrderList(params){
        const {user_id,PageNum,PageSize,status}=params
        const offset = (PageNum - 1) * PageSize
        console.log('测试',PageNum,PageSize,offset,status,user_id)
         let pageLimit= PageSize*1
         const{count , rows} =await Order.findAndCountAll({where:{user_id,status:status*1},
            offset:offset,
            limit:pageLimit,
            attributes:["id", 
            "address_id",
            "goods_info",
            "total",
            "order_number",
            "status"]
        })
         
         return {
            PageNum,
            PageSize,
            total:count,
            List:rows
        }
    }

更新列表

更新订单

order.router.js

js 复制代码
  async  updateOrder(id,user_id,state){
        const res = await Order.update({status:state},{where:{id,user_id}})
        return res ? 'success' : 'fail';
    }

order.controller.js

JS 复制代码
async update(ctx){
   const id = ctx.params.id;
   const user_id = ctx.state.user.id;
   const  {state}= ctx.request.body;
   const res= await updateOrder(id,user_id,state);
   ctx.body={
      code:200,
      message:'更新订单成功',
      data:res
   }
  }

order.service.js

JS 复制代码
 async  updateOrder(id,user_id,state){
        const res = await Order.update({status:state},{where:{id,user_id}})
        return res ? 'success' : 'fail';
    }
}
相关推荐
小_太_阳36 分钟前
Scala_【1】概述
开发语言·后端·scala·intellij-idea
百万蹄蹄向前冲36 分钟前
2024不一样的VUE3期末考查
前端·javascript·程序员
智慧老师1 小时前
Spring基础分析13-Spring Security框架
java·后端·spring
轻口味1 小时前
【每日学点鸿蒙知识】AVCodec、SmartPerf工具、web组件加载、监听键盘的显示隐藏、Asset Store Kit
前端·华为·harmonyos
alikami1 小时前
【若依】用 post 请求传 json 格式的数据下载文件
前端·javascript·json
吃杠碰小鸡2 小时前
lodash常用函数
前端·javascript
emoji1111112 小时前
前端对页面数据进行缓存
开发语言·前端·javascript
泰伦闲鱼2 小时前
nestjs:GET REQUEST 缓存问题
服务器·前端·缓存·node.js·nestjs
m0_748250032 小时前
Web 第一次作业 初探html 使用VSCode工具开发
前端·html
一个处女座的程序猿O(∩_∩)O2 小时前
vue3 如何使用 mounted
前端·javascript·vue.js