动手学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';
    }
}
相关推荐
万少20 分钟前
鸿蒙创新赛 HarmonyOS 6.0.0(20) 关键特性汇总
前端
桦说编程26 分钟前
爆赞!完全认同!《软件设计的哲学》这本书深得我心
后端
thinktik37 分钟前
还在手把手教AI写代码么? 让你的AWS Kiro AI IDE直接读飞书需求文档给你打工吧!
后端·serverless·aws
还有多远.38 分钟前
jsBridge接入流程
前端·javascript·vue.js·react.js
蝶恋舞者1 小时前
web 网页数据传输处理过程
前端
非凡ghost1 小时前
FxSound:提升音频体验,让音乐更动听
前端·学习·音视频·生活·软件需求
吃饭最爱1 小时前
html的基础知识
前端·html
我没想到原来他们都是一堆坏人1 小时前
(未完待续...)如何编写一个用于构建python web项目镜像的dockerfile文件
java·前端·python
前端Hardy2 小时前
HTML&CSS:有趣的漂流瓶
前端·javascript·css