1、项目搭建
npm init -y
2、启动服务
安装依赖
npm i dotenv koa
npm i nodemon -D
目录结构
.env
APP_PROT=8000
config.default.js
const dotenv = require('dotenv')
// 将.env的属性值解析到此处
dotenv.config()
module.exports = process.env
main.js
const Koa = require('koa')
const app = new Koa()
const { APP_PROT } = require('./config/config.default')
app.use((ctx, next) => {
ctx.body = 'hello api'
})
app.listen(APP_PROT, () => {
console.log(`端口号为:${APP_PROT}`);
})
package.json
"scripts": {
"dev":"nodemon ./src/main.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
3、添加路由
安装依赖
npm i koa-router
目录结构
user.route.js
const Router = require('koa-router')
const router = new Router({ prefix: '/users' })
// GET /users /
router.get('/', (ctx, next) => {
ctx.body = 'hello user'
})
module.exports = router
main.js
const Koa = require('koa')
const app = new Koa()
const { APP_PROT } = require('./config/config.default')
const userRouter = require('./router/user.route')
//注册路由中间件
app.use(userRouter.routes())
app.listen(APP_PROT, () => {
console.log(`端口号为:${APP_PROT}`);
})
4、目录结构优化
目录结构
app/index.js
const Koa = require('koa')
const app = new Koa()
const userRouter = require('../router/user.route')
app.use(userRouter.routes())
module.exports = app
controller/user.controller.js
class UserController {
async register(ctx, next) {
ctx.body = '用户注册成功'
}
async login(ctx, next) {
ctx.body = '登录成功'
}
}
module.exports = new UserController()
router/user.route.js
const Router = require('koa-router')
const router = new Router({ prefix: '/users' })
const { register, login } = require('../controller/user.controller')
// GET /users /
router.post('/register', register)
router.post('/login', login)
module.exports = router
main.js
const app = require('./app')
const { APP_PROT } = require('./config/config.default')
app.listen(APP_PROT, () => {
console.log(`端口号为:${APP_PROT}`);
})
此处post请求可用postman来验证
5、解析body拆分service层
koa-body 是一个可以帮助解析 http 中 body 的部分的中间件,包括 json、表单、文本、文件等
安装依赖
npm i koa-body
目录结构
app/index.js
const Koa = require('koa')
const { koaBody } = require('koa-body')
const app = new Koa()
const userRouter = require('../router/user.route')
// 将请求的方式
app.use(koaBody())
app.use(userRouter.routes())
module.exports = app
service/user.service.js
class UserService {
async createUser(userName, passWord) {
// 写入数据库
return '写入数据库成功'
}
}
module.exports = new UserService()
controller
const { createUser } = require('../service/user.service')
class UserController {
async register(ctx, next) {
// 1、获取数据
const { userName, passWord } = ctx.request.body
// 2、操作数据库
console.log(userName, passWord);
const res = await createUser(userName, passWord)
// 3、返回结果
ctx.body = res
}
async login(ctx, next) {
ctx.body = '登录成功'
}
}
module.exports = new UserController()
postman发送带参数的post请求
6、集成sequlize
安装依赖
npm i sequelize mysql2
目录结构
.env
APP_PROT=8000
MYSQL_HOST = localhost
MYSQL_PORT = 3306
MYSQL_USER = root
MYSQL_PWD = asd15132609768.
MYSQL_DB = koa-dev
seq.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((error) => {
console.log(error);
})
*/
module.exports = seq
7、创建用户模型
表模型
目录结构
model/user.model.js
const { DataTypes } = require('sequelize')
const seq = require('../db/seq')
const User = seq.define('user', {
// id会自动生成
userName: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
comment: '用户名, 唯一'
},
passWord: {
type: DataTypes.CHAR(64),
allowNull: false,
comment: '‘密码'
},
isAdmin: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: 0,
comment: '是否为管理员,0: 不是管理员(默认). 1: 是管理员'
}
})
// 当force为true时表示如果表已经存在,则将其首先删除
User.sync({ force: false })
module.exports = User
8、添加用户
service/user.service.js
const User = require('../model/user.model')
class UserService {
async createUser(userName, passWord) {
// 写入数据库
const res = await User.create({ userName, passWord })
return res.dataValues
}
}
module.exports = new UserService()
controller/user.controller.js
const { createUser } = require('../service/user.service')
class UserController {
async register(ctx, next) {
// 1、获取数据
const { userName, passWord } = ctx.request.body
// 2、操作数据库
const res = await createUser(userName, passWord)
// 3、返回结果
ctx.body = {
code: 0,
message: '用户注册成功',
result: {
id: res.id,
userName: res.userName
}
}
console.log(ctx.body);
}
async login(ctx, next) {
ctx.body = '登录成功'
}
}
module.exports = new UserController()
打印结果为
9、错误处理
service/user.service.js
const User = require('../model/user.model')
class UserService {
async createUser(userName, passWord) {
// 写入数据库
const res = await User.create({ userName, passWord })
return res.dataValues
}
async getUserInfo({ id, userName, passWord, isAdmin }) {
const whereOpt = {}
id && Object.assign(whereOpt, { id })
userName && Object.assign(whereOpt, { userName })
passWord && Object.assign(whereOpt, { passWord })
isAdmin && Object.assign(whereOpt, { isAdmin })
//findOne和js中的find一样,findAll则和filter一样
const res = await User.findOne({
// 返回哪些属性
attributes: ['id', 'userName'],
// 查询条件
where: whereOpt
})
return res ? res.dataValues : null
}
}
module.exports = new UserService()
controller/user.controller.js
const { createUser, getUserInfo } = require('../service/user.service')
class UserController {
async register(ctx, next) {
// 1、获取数据
const { userName, passWord } = ctx.request.body
// 合法性
if (!userName || !passWord) {
console.error('用户名或密码为空', ctx.request.body)
ctx.status = 400
ctx.body = {
code: '10001',
message: '用户名或密码为空',
result: ''
}
return
}
console.log(111, await getUserInfo({ userName }));
// 合理性(不能创建相同的用户)
if (await getUserInfo({ userName })) {
ctx.status = 409
ctx.body = {
code: '10002',
message: '用户已经存在',
result: '',
}
return
}
// 2、操作数据库
const res = await createUser(userName, passWord)
// 3、返回结果
ctx.body = {
code: 0,
message: '用户注册成功',
result: {
id: res.id,
userName: res.userName
}
}
}
async login(ctx, next) {
ctx.body = '登录成功'
}
}
module.exports = new UserController()
10、拆分中间件
文件目录
错误处理封装
constant/err.type.js
module.exports = {
useFormateError: {
code: '10001',
message: '用户名或密码为空',
result: ''
},
userAlreadyExited: {
code: '10002',
message: '用户名已经存在',
result: ''
},
userRegisterError: {
code: '10003',
message: '用户注册失败',
result: ''
}
}
将一些请求的验证抽出来
middleware/user.model.js
const { getUserInfo } = require("../service/user.service")
const { useFormateError, userAlreadyExited, userRegisterError } = require('../constant/err.type')
const userValidator = async (ctx, next) => {
const { userName, passWord } = ctx.request.body
if (!userName || !passWord) {
console.error('用户名或密码为空', ctx.request.body)
ctx.app.emit("error", useFormateError, ctx)
return
}
await next()
}
const verifyUser = async (ctx, next) => {
const { userName } = ctx.request.body
try {
const res = await getUserInfo({ userName })
if (res) {
console.error('用户名已存在', { userName })
ctx.app.emit("error", userAlreadyExited, ctx)
return
}
} catch (e) {
console.error('获取用户信息失败', e);
ctx.app.emit('error', userRegisterError, ctx)
}
await next()
}
module.exports = {
userValidator,
verifyUser
}
将中间件引入放在register之前,这样在注册前会先执行中间件的代码
router/user.route.js
const Router = require('koa-router')
const router = new Router({ prefix: '/users' })
const { userValidator, verifyUser } = require('../middleware/user.middleware')
const { register, login } = require('../controller/user.controller')
// GET /users /
router.post('/register', userValidator, verifyUser, register)
router.post('/login', login)
module.exports = router
code处理
app/errorHandler.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
}
通过“error”字符串进行监听,并进行错误统一处理
app/index.js
const Koa = require('koa')
const { koaBody } = require('koa-body')
const errHandle = require('./errorHandler')
const app = new Koa()
const userRouter = require('../router/user.route')
app.use(koaBody())
app.use(userRouter.routes())
// 统一的错误处理
app.on('error', errHandle)
module.exports = app
11、密码加密
下载依赖
npm i bcryptjs
middleware/user.middleware.js
const cryptPassword = 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()
}
module.exports = {
userValidator,
verifyUser,
cryptPassword
}
router/user.route.js
const { userValidator, verifyUser, cryptPassword } = require('../middleware/user.middleware')
// GET /users /
router.post('/register', userValidator, verifyUser, cryptPassword, register)
12、验证登录
constant/err.type.js
userDoesNotExist: {
code: '10004',
message: '用户不存在',
result: ''
},
userLoginError: {
code: '10005',
message: '用户登录失败',
result: ''
},
invalidPassword: {
code: '10006',
message: '密码不匹配',
result: ''
}
middleware/user.middleware.js
const verifyLogin = async (ctx, next) => {
const { userName, passWord } = ctx.request.body
try {
const res = await getUserInfo({ userName })
if (!res) {
console.error('用户名不存在', { userName });
ctx.app.emit('error', userDoesNotExist, ctx)
return
}
if (!bcrypt.compareSync(passWord, res.passWord)) {
ctx.app.emit('error', invalidPassword, ctx)
return
}
} catch (e) {
console.error(e);
return ctx.app.emit('error', userLoginError, ctx)
}
await next()
}
router/user.route.js
router.post('/login', userValidator, verifyLogin, login)
测试
13、颁发token
下载依赖
npm i jsonwebtoken
.env
JWT_SECRET = xzd
controller/user.controller.js
const jwt = require('jsonwebtoken')
const { JWT_SECRET } = require('../config/config.default')
async login(ctx, next) {
const { userName } = ctx.request.body
try {
// 从返回结果对象中剔除password属性,将剩下的属性放到res对象
const { passWord, ...res } = await getUserInfo({ userName })
ctx.body = {
code: 0,
message: '用户登录成功',
result: {
// JWT_SECRET是密钥,expiresIn后面跟的属性是过期时间,1d表示1天
token: jwt.sign(res, JWT_SECRET, { expiresIn: '1d' })
}
}
} catch (e) {
console.error('用户登录失败', e);
}
}
测试结果
14、用户认证
目录结构
constant/err.type.js
tokenExpiredError: {
code: '10101',
message: 'token已过期',
result: ''
},
invalidToken: {
code: '10102',
message: '无效的token',
result: ''
}
middleware/auth.middleware.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
// 去除Bearer字段及空格
const token = authorization.replace('Bearer', '').replace(/\s+/g, "");
try {
// 获取id、userName、
const user = jwt.verify(token, JWT_SECRET)
ctx.state.user = user
} catch (e) {
switch (e.name) {
case 'TokenExpiredError':
console.error('token已过期', e);
return ctx.app.emit('error', tokenExpiredError, ctx)
case 'JsonWebTokenError':
console.error('无效的token', e);
return ctx.app.emit('error', invalidToken, ctx)
}
}
await next()
}
module.exports = {
auth
}
router/user.service.js
const { auth } = require('../middleware/auth.middleware')
router.patch('/', auth, (ctx, next) => {
ctx.body = '修改密码成功'
})
验证
- 方法一
- 方法二
15、修改密码
controller/user.controller.js
async changePassword(ctx, next) {
const { id } = ctx.state.user
const passWord = ctx.request.body.passWord
console.log(6666, passWord);
if (await updateById({ id, passWord })) {
ctx.body = {
code: 0,
message: '修改密码成功',
result: ''
}
} else {
ctx.body = {
code: '10007',
message: '修改密码失败',
result: ""
}
}
}
service/user.service.js
async updateById({ id, userName, passWord, isAdmin }) {
const whereOpt = { id }
const newUser = {}
userName && Object.assign(newUser, { userName })
passWord && Object.assign(newUser, { passWord })
isAdmin && Object.assign(newUser, { isAdmin })
// 用newUser里的字段数据覆盖掉查询出来数据的字段数据(id、password)
const res = await User.update(newUser, { where: whereOpt })
return !!res
}
16、商品模块
目录结构
koa
├─ .env
├─ README.md
├─ package-lock.json
├─ package.json
└─ src
├─ app
│ ├─ errorHandler.js
│ └─ index.js
├─ config
│ └─ config.default.js
├─ constant
│ └─ err.type.js
├─ controller
│ ├─ goods.controller.js
│ └─ user.controller.js
├─ db
│ └─ seq.js
├─ main.js
├─ middleware
│ ├─ auth.middleware.js
│ └─ user.middleware.js
├─ model
│ └─ user.model.js
├─ router
│ ├─ goods.route.js
│ └─ user.route.js
└─ service
└─ user.service.js
controller/goods.controller.js
class GoodsController {
async upload(ctx, next) {
ctx.body = '商品图片上传成功'
}
}
module.exports = new GoodsController()
router/goods.route.js
const Router = require('koa-router')
const { upload } = require('../controller/goods.controller')
const router = new Router({ prefix: '/goods' })
router.post('/upload', upload)
module.exports = router
app/index.js
const Koa = require('koa')
const { koaBody } = require('koa-body')
const errHandle = require('./errorHandler')
const app = new Koa()
const userRouter = require('../router/user.route')
const goodsRouter = require('../router/goods.route')
app.use(koaBody())
app.use(userRouter.routes())
app.use(goodsRouter.routes())
// 统一的错误处理
app.on('error', errHandle)
module.exports = app
17、路由自动加载
每次在创建新的路由时,都得在app/index.js里面注册新的路由,这样会很麻烦,所以可以采用以下方法来进行路由的自动注册
目录结构
koa
├─ .env
├─ README.md
├─ package-lock.json
├─ package.json
└─ src
├─ app
│ ├─ errorHandler.js
│ └─ index.js
├─ config
│ └─ config.default.js
├─ constant
│ └─ err.type.js
├─ controller
│ ├─ goods.controller.js
│ └─ user.controller.js
├─ db
│ └─ seq.js
├─ main.js
├─ middleware
│ ├─ auth.middleware.js
│ └─ user.middleware.js
├─ model
│ └─ user.model.js
├─ router
│ ├─ goods.route.js
│ ├─ index.js
│ └─ user.route.js
└─ service
└─ user.service.js
router/index.js
const fs = require('fs')
const Router = require('koa-router')
const router = new Router()
// 参数file的值为当前目录里的所有文件夹:goods.route.js、user.route.js、index.js...
fs.readdirSync(__dirname).forEach(file => {
// 过滤掉index.js
if (file !== 'index.js') {
let readRoute = require(`./${file}`)
// router相当于一个容器,把每一个单独的路由注册到router容器里
router.use(readRoute.routes())
}
})
module.exports = router
app/index.js
const Koa = require('koa')
const { koaBody } = require('koa-body')
const errHandle = require('./errorHandler')
const app = new Koa()
const router = require('../router')
app.use(koaBody())
app.use(router.routes())
// 当使用没有注册过的接口请求时会报501错误,不加这行代码之前是报404错误
app.use(router.allowedMethods())
// 统一的错误处理
app.on('error', errHandle)
module.exports = app
18 、封装管理员权限
constant/err.type.js
hasNotAdminPermission: {
code: '10103',
message: '没有管理员权限',
result: ''
}
middleware/auth.middleware.js
const hadAdminPermission = async (ctx, next) => {
const { isAdmin } = ctx.state.user
if (!isAdmin) {
console.error('该用户没有管理员的权限', ctx.state.user);
return ctx.app.emit('error', hasNotAdminPermission, ctx)
}
await next()
}
router/goods.route.js
const { auth, hadAdminPermission } = require('../middleware/auth.middleware')
router.post('/upload', auth, hadAdminPermission, upload)
19、添加图片上传
安装依赖
npm i koa-static
目录结构
koa
├─ .env
├─ README.md
├─ package-lock.json
├─ package.json
├─ src
│ ├─ app
│ │ ├─ errorHandler.js
│ │ └─ index.js
│ ├─ config
│ │ └─ config.default.js
│ ├─ constant
│ │ └─ err.type.js
│ ├─ controller
│ │ ├─ goods.controller.js
│ │ └─ user.controller.js
│ ├─ db
│ │ └─ seq.js
│ ├─ main.js
│ ├─ middleware
│ │ ├─ auth.middleware.js
│ │ ├─ goods.middleware.js
│ │ └─ user.middleware.js
│ ├─ model
│ │ └─ user.model.js
│ ├─ router
│ │ ├─ goods.route.js
│ │ ├─ index.js
│ │ └─ user.route.js
│ ├─ service
│ │ └─ user.service.js
│ └─ upload
│ ├─ 8a78e85c4f1f08c9b5c299200.jpg
│ ├─ 988c39d9bb4916c94e1d5d300.md
│ ├─ b6c681b6b9fe35601f8cde100.jpg
│ ├─ ca676cff8e47d1a39042e1d00.jpg
│ ├─ d8e0d200559cca0b33b4a3700.md
│ ├─ e8c2cc0f61dc651bc7a63cf00.md
│ ├─ fa3078c3b98dd2f4182f14201.jpg
│ ├─ fa3078c3b98dd2f4182f14202.jpg
│ └─ fe5d803c345288d2ade393000.jpg
└─ test
└─ index.html
文件内容
constant/err.type.js
fileUploadError: {
code: '10201',
message: '商品图片上传失败',
result: ''
},
unSupportedFileType: {
code: '10202',
message: '不支持的文件格式',
result: ''
}
app/index.js
// 插件作用:能在服务器内输入 域名/端口号/图片名称 打开文件内容
const serve = require('koa-static')
app.use(koaBody({
multipart: true,
formidable: {
/**
* 在配置选项option里,不推荐使用相对路径
* 在option里的相对路径,不是相对的当前文件,相对process.cwd(),可以将process.cwd()打印下试试
*/
uploadDir: path.join(__dirname, '../upload'),
// 是否保持原来文件的后缀名
keepExtensions: true
}
}))
// 配置打开文件内容的路径
app.use(serve(path.join(__dirname, '../upload')))
controller/goods.controller.js
const path = require('path')
const { fileUploadError, unSupportedFileType } = require('../constant/err.type')
class GoodsController {
async upload(ctx, next) {
const { file } = ctx.request.files || []
const fileTypes = ['image/jpeg', 'image/png']
if (file) {
if (!fileTypes.includes(file.mimetype)) {
return ctx.app.emit('error', unSupportedFileType, ctx)
}
ctx.body = {
code: 0,
message: '商品图片上传成功',
result: {
goodsImg: path.basename(file.filepath)
}
}
} else {
return ctx.app.emit('error', fileUploadError, ctx)
}
}
}
module.exports = new GoodsController()
测试
- 在
postman里进行配置
- 可以写一个简单的前端页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<form
action="http://localhost:8000/goods/upload"
method="post"
enctype="multipart/form-data"
>
<input type="file" name="file" />
<input type="submit" value="上传" />
</form>
</body>
</html>
20、入参格式验证
安装依赖
npm i koa-parameter
目录结构
koa
├─ .env
├─ README.md
├─ package-lock.json
├─ package.json
└─ src
├─ app
│ ├─ errorHandler.js
│ └─ index.js
├─ config
│ └─ config.default.js
├─ constant
│ └─ err.type.js
├─ controller
│ ├─ goods.controller.js
│ └─ user.controller.js
├─ db
│ └─ seq.js
├─ main.js
├─ middleware
│ ├─ auth.middleware.js
│ ├─ goods.middleware.js
│ └─ user.middleware.js
├─ model
│ └─ user.model.js
├─ router
│ ├─ goods.route.js
│ ├─ index.js
│ └─ user.route.js
├─ service
│ └─ user.service.js
└─ upload
└─ fe5d803c345288d2ade393000.jpg
文件内容
constant/err.type.js
goodsFormatError: {
code: '10203',
message: '商品参数格式错误',
result: ''
}
app/index.js
const parameter = require('koa-parameter')
// 放在router前面,将app当参数传进去,这样下面的route就可以用ctx里的verifyParams属性了
app.use(parameter(app))
app.use(router.routes())
middleware/goods.middleware.js
const { goodsFormatError } = require("../constant/err.type");
const validator = async (ctx, next) => {
try {
// verifyParams为插件里的属性
ctx.verifyParams({
// 入参时goodsName为必填,且为stirng类型
goodsName: { type: 'string', require: true },
goodsPrice: { type: 'number', require: true },
goodsNum: { type: 'number', require: true },
goodsImg: { type: 'string', require: true },
})
} catch (err) {
console.error(err);
goodsFormatError.result = err
return ctx.app.emit('error', goodsFormatError, ctx)
}
await next()
}
module.exports = {
validator
}
router/goods.route.js
// 发布商品接口
router.post('/', auth, hadAdminPermission, validator, (ctx, next) => {
ctx.body = '发布商品成功'
})
测试
当故意传错一个参数时,会发现插件会自动进行验证并给出提示信息
21、发布商品写入数据库
目录结构
koa
├─ .env
├─ README.md
├─ package-lock.json
├─ package.json
└─ src
├─ app
│ ├─ errorHandler.js
│ └─ index.js
├─ config
│ └─ config.default.js
├─ constant
│ └─ err.type.js
├─ controller
│ ├─ goods.controller.js
│ └─ user.controller.js
├─ db
│ └─ seq.js
├─ main.js
├─ middleware
│ ├─ auth.middleware.js
│ ├─ goods.middleware.js
│ └─ user.middleware.js
├─ model
│ ├─ goods.model.js
│ └─ user.model.js
├─ router
│ ├─ goods.route.js
│ ├─ index.js
│ └─ user.route.js
├─ service
│ ├─ goods.service.js
│ └─ user.service.js
└─ upload
└─ fe5d803c345288d2ade393000.jpg
文件内容
model/goods.model.js
const { DataTypes } = require('sequelize')
const seq = require('../db/seq')
const User = seq.define('user', {
// id会自动生成
userName: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
comment: '用户名, 唯一'
},
passWord: {
type: DataTypes.CHAR(64),
allowNull: false,
comment: '‘密码'
},
isAdmin: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: 0,
comment: '是否为管理员,0: 不是管理员(默认). 1: 是管理员'
}
})
// 当force为true时表示如果表已经存在,则将其首先删除
User.sync({ force: false })
module.exports = User
执行node src/model/goods.model.js指令创建goods表
service/goods.service.js
const Goods = require('../model/goods.model')
class GoodsService {
async createGoods(goods) {
const res = await Goods.create(goods)
return res.dataValues
}
}
module.exports = new GoodsService()
controller/goods.controller.js
async create(ctx, next) {
try {
const { createdAt, updatedAt, ...res } = await createGoods(ctx.request.body)
ctx.body = {
code: 0,
message: '发布成功',
result: res
}
} catch (err) {
console.error(err);
return ctx.ap.emit('error', publishGoodsError, ctx)
}
}
router/goods.route.js
router.post('/', auth, hadAdminPermission, validator, create)
22、修改商品接口
constant/err.type.js
invalidGoodsID: {
code: '10205',
message: '待修改的商品不存在',
result: ''
}
service/goods.service.js
async updateGoods(id, goods) {
const res = await Goods.update(goods, { where: { id } })
return res[0] > 0
}
controller/goods.controller.js
async update(ctx) {
try {
const res = await updateGoods(ctx.params.id, ctx.request.body)
if (res) {
ctx.body = {
code: 0,
message: '修改商品成功',
result: ''
}
} else {
return ctx.app.emit('error', invalidGoodsID, ctx)
}
} catch (err) {
console.error(err);
}
}
router/goods.route.js
router.put('/:id', auth, hadAdminPermission, validator, update)
23、硬删除接口
service/goods.service.js
async removeGoods(id) {
const res = await Goods.destroy({ where: { id } })
return res > 0
}
controller/goods.controller.js
async remove(ctx) {
try {
const res = await removeGoods(ctx.params.id)
if (res) {
ctx.body = {
code: 0,
message: '删除商品成功',
result: ''
}
} else {
return ctx.app.emit('error', invalidGoodsID, ctx)
}
} catch (err) {
console.error(err);
}
}
route/goods.route.js
router.delete('/:id', auth, hadAdminPermission, remove)
24、商品的上架与下架
直接硬删除商品这种方法不是很提倡,可以用商品的上架和下架来代替
需要在goods表中添加一个deleteAt字段
model/user.route.js
const Goods = seq.define('goods', {
goodsName: {
type: DataTypes.STRING,
allowNull: false,
comment: '商品名称'
},
goodsPrice: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
comment: '商品价格'
},
goodsNum: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '商品图片的url'
},
goodsImg: {
type: DataTypes.STRING,
allowNull: false,
comment: '商品图片url地址'
},
},
// 添加这么个玩意儿,paranoid是固定字段
{
paranoid: true
})
service/goods.service.js
async removeGoods(id) {
const res = await Goods.destroy({ where: { id } })
return res > 0
}
async restoreGoods(id) {
const res = await Goods.restore({ where: { id } })
return res > 0
}
controller/goods.controller.js
async remove(ctx) {
try {
const res = await removeGoods(ctx.params.id)
if (res) {
ctx.body = {
code: 0,
message: '下架商品成功',
result: ''
}
} else {
return ctx.app.emit('error', invalidGoodsID, ctx)
}
} catch (err) {
console.error(err);
}
}
async restore(ctx) {
const res = await restoreGoods(ctx.params.id)
if (res) {
ctx.body = {
code: 0,
message: '上架商品成功',
result: ''
}
} else {
return ctx.app.emit('error', invalidGoodsID, ctx)
}
}
router/goods.route.js
// 商品上架和下架来代替硬删除
router.post('/off/:id', auth, hadAdminPermission, remove)
router.post('/on/:id', auth, hadAdminPermission, restore)
测试
- 下架商品
- 上架商品
25、商品列表分页
route/goods.toute.js
// 商品列表分页数据
router.get('/', goodsList)
controller/goods.controller.js
async goodsList(ctx) {
// 解析pageNum(第几页)和pageSize(一页有多少数据)
const { pageNum = 1, pageSize = 10 } = ctx.request.query
// 调用数据处理的相关方法
const res = await getGoodsList(pageNum, pageSize)
// 返回结果
ctx.body = {
code: 0,
message: '获取商品列表成功',
result: res
}
}
service/goods.service
async getGoodsList(pageNum, pageSize) {
// 获取表中的数据数量,用来显示有多少页数据
const count = await Goods.count()
// 获取分页当中的偏移量offset(从第几个数据开始)
const offset = (pageNum - 1) * pageSize
// 获取分页的具体数据
const rows = await Goods.findAll({ offset, limit: Number(pageSize) })
return {
pageNum,
pageSize,
total: count,
list: rows
}
}
上述方法可以用另外一种方法findAndCountAll进行整合
async getGoodsList(pageNum, pageSize) {
// 获取分页当中的偏移量offset(从第几个数据开始)
const offset = (pageNum - 1) * pageSize
// 获取分页的具体数据
const { count, rows } = await Goods.findAndCountAll({ offset, limit: Number(pageSize) })
return {
pageNum,
pageSize,
total: count,
list: rows
}
}
查询时,当商品下架时,会将其自动过滤掉
26、添加购物车
具体功能:每请求一次接口,如果存在这个商品就在此的基础上加一,如若不存在就创建一个新的数据
目录结构
koa
├─ .env
├─ README.md
├─ package-lock.json
├─ package.json
└─ src
├─ app
│ ├─ errorHandler.js
│ └─ index.js
├─ config
│ └─ config.default.js
├─ constant
│ └─ err.type.js
├─ controller
│ ├─ cart.controller.js
│ ├─ goods.controller.js
│ └─ user.controller.js
├─ db
│ └─ seq.js
├─ main.js
├─ middleware
│ ├─ auth.middleware.js
│ ├─ cart.middleware.js
│ ├─ goods.middleware.js
│ └─ user.middleware.js
├─ model
│ ├─ cart.model.js
│ ├─ goods.model.js
│ └─ user.model.js
├─ router
│ ├─ cart.route.js
│ ├─ goods.route.js
│ ├─ index.js
│ └─ user.route.js
├─ service
│ ├─ cart.service.js
│ ├─ goods.service.js
│ └─ user.service.js
└─ upload
└─ fe5d803c345288d2ade393000.jpg
model/cat.model.js
const { DataTypes } = require('sequelize')
const seq = require('../db/seq')
const Cart = seq.define('cart', {
goodsId: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '商品的id'
},
userId: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '用户的id'
},
number: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 1,
comment: '商品的数量'
},
selected: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true,
comment: '是否选中'
}
})
Cart.sync({ force: false })
module.exports = Cart
service/cart.service.js
const { Op } = require('sequelize')
const Cart = require('../model/cart.model')
class CartService {
async createOrUpdate(userId, goodsId) {
let res = await Cart.findOne({
where: {
// 查询两个条件
[Op.and]: {
userId,
goodsId
}
}
})
if (res) {
// 已经存在一条记录就加1
await res.increment('number')
return await res.reload()
} else {
// 否则就重新创建一条记录
return await Cart.create({
userId,
goodsId
})
}
}
}
module.exports = new CartService()
controller/cart.controller.js
const { createOrUpdate } = require('../service/cart.service')
class CartController {
async add(ctx) {
const userId = ctx.state.user.id
const goodsId = ctx.request.body.goodsId
const res = await createOrUpdate(userId, goodsId)
ctx.body = {
code: 0,
message: '添加到购物车成功',
result: res
}
}
}
module.exports = new CartController()
middleware/cart.middleware.js
const { invalidGoodsID } = require('../constant/err.type')
const validator = async (ctx, next) => {
try {
ctx.verifyParams({
// goodsId: { type: 'number', require: true }的简化写法
goodsId: 'number'
})
} catch (err) {
console.error(err);
invalidGoodsID.result = err
return ctx.app.emit('error', invalidGoodsID, ctx)
}
await next()
}
module.exports = {
validator
}
router/cat.route.js
const Router = require('koa-router')
const { add } = require('../controller/cart.controller')
const { auth } = require('../middleware/auth.middleware')
const { validator } = require('../middleware/cart.middleware')
const router = new Router({ prefix: '/cart' })
router.post('/', auth, validator, add)
module.exports = router
27、获取购物车列表(添加表关联 一对一)
具体实现的功能为将
将这里的goodsId与goods表中id对应起来
router/cart.model.js
router.get('/', auth, cartList)
controller/cart.controler.js
async cartList(ctx) {
const { pageNum = 1, pageSize = 10 } = ctx.request.body
const res = await getCartList(pageNum, pageSize)
ctx.body = {
code: 0,
message: '获取购物车列表成功',
result: res
}
}
添加表关联
model/cart.model.js
Cart.belongsTo(Goods, {
foreignKey: 'goodsId',
// 重命名
as: 'goodsInfo'
})
module.exports = Cart
service/cart.service.js
async getCartList(pageNum, pageSize) {
const offset = (pageNum - 1) * pageSize
const { count, rows } = await Cart.findAndCountAll({
attributes: ['id', 'number', 'selected'],
offset: offset,
limit: pageSize * 1,
include: {
model: Goods,
as: 'goodsInfo',
// 只返回以下属性
attributes: ['id', 'goodsName', 'goodsPrice', 'goodsImg']
}
})
return {
pageNum,
pageSize,
total: count,
list: rows
}
}
数据测试
cart表可参照开头那张图
goods表
返回的数据为
{
"code": 0,
"message": "获取购物车列表成功",
"result": {
"pageNum": 1,
"pageSize": 10,
"total": 5,
"list": [
{
"id": 1,
"number": 1,
"selected": true,
"goodsInfo": {
"id": 2,
"goodsName": "蓝牙音响",
"goodsPrice": "100.00",
"goodsImg": "8a78e85c4f1f08c9b5c299200.jpg"
}
},
{
"id": 2,
"number": 2,
"selected": true,
"goodsInfo": null
},
{
"id": 3,
"number": 1,
"selected": true,
"goodsInfo": {
"id": 1,
"goodsName": "蓝牙音响",
"goodsPrice": "100.00",
"goodsImg": "8a78e85c4f1f08c9b5c299200.jpg"
}
},
{
"id": 4,
"number": 1,
"selected": true,
"goodsInfo": {
"id": 3,
"goodsName": "蓝牙音响",
"goodsPrice": "100.00",
"goodsImg": "8a78e85c4f1f08c9b5c299200.jpg"
}
},
{
"id": 5,
"number": 1,
"selected": true,
"goodsInfo": {
"id": 3,
"goodsName": "蓝牙音响",
"goodsPrice": "100.00",
"goodsImg": "8a78e85c4f1f08c9b5c299200.jpg"
}
}
]
}
}
28、更新购物车
更新购物车的number值和selected值
改造验证
middleware/cart.middleware.js
const validator = (rules) => {
return async (ctx, next) => {
try {
ctx.verifyParams(rules)
} catch (err) {
console.error(err);
cartFormatError.result = err
return ctx.app.emit('error', cartFormatError, ctx)
}
await next()
}
}
router/cart.route.js
router.post('/', auth, validator({ goodsId: 'number' }), add)
router.patch('/:id', auth, validator({
number: { type: 'number', required: false },
selected: { type: 'bool', required: false }
}), update)
controller/cart.controller.js
async update(ctx) {
const { id } = ctx.request.params
const { number, selected } = ctx.request.body
if (number === undefined && selected === undefined) {
cartFormatError.message = 'number和selected不能同时为空'
return ctx.app.emit('error', cartFormatError, ctx)
}
const res = await updateCart({ id, number, selected })
ctx.body = {
code: 0,
message: '更新购物车成功',
result: res
}
}
service/cart.service.js
async updateCart(params) {
const { id, number, selected } = params
// 根据id来进行数据查询
const res = await Cart.findByPk(id)
if (!res) return ''
// 修改值并添加默认值
number !== undefined ? (res.number = number) : ''
selected !== undefined ? (res.selected = selected) : true
// 修改完成后保存
return await res.save()
}
27、删除购物车
批量删除,传递的参数是一个数组
router/cart.route.js
router.delete('/', auth, validator({ ids: 'array' }), remove)
app/index.js
app.use(koaBody({
parsedMethods: ['POST', 'PUT', 'PATCH', 'DELETE']
}))
controller/cart.controller.js
async remove(ctx) {
const { ids } = ctx.request.body
const res = await removeCart(ids)
ctx.body = {
code: 0,
message: '删除购物车成功',
result: res
}
}
servcie/cart.service.js
async removeCart(ids) {
return await Cart.destroy({
where: {
id: {
[Op.in]: ids
}
}
})
}
测试结果
28、全选和取消全选
- 全选
router/cart.toute.js
router.post('/selectAll', auth, selectAll)
controller/cart.controller.js
async selectAll(ctx) {
const userId = ctx.state.user.id
const res = await selectAllCart(userId)
ctx.body = {
code: 0,
message: '全部选中',
result: res
}
}
service/cart.service.js
async selectAllCart(userId) {
console.log(111, userId);
return await Cart.update(
{ selected: true },
{
where: {
userId
}
}
)
}
- 取消全选与全选一样,并且可以进行整合成一个方法,我懒得整了
29、添加地址接口
目录结构
koa
├─ .env
├─ README.md
├─ package-lock.json
├─ package.json
└─ src
├─ app
│ ├─ errorHandler.js
│ └─ index.js
├─ config
│ └─ config.default.js
├─ constant
│ └─ err.type.js
├─ controller
│ ├─ address.controller.js
│ ├─ cart.controller.js
│ ├─ goods.controller.js
│ └─ user.controller.js
├─ db
│ └─ seq.js
├─ main.js
├─ middleware
│ ├─ address.middleware.js
│ ├─ auth.middleware.js
│ ├─ cart.middleware.js
│ ├─ goods.middleware.js
│ └─ user.middleware.js
├─ model
│ ├─ address.model.js
│ ├─ cart.model.js
│ ├─ goods.model.js
│ └─ user.model.js
├─ router
│ ├─ address.route.js
│ ├─ cart.route.js
│ ├─ goods.route.js
│ ├─ index.js
│ └─ user.route.js
├─ service
│ ├─ address.service.js
│ ├─ cart.service.js
│ ├─ goods.service.js
│ └─ user.service.js
└─ upload
└─ fe5d803c345288d2ade393000.jpg
文件内容
router/address.router.js
const Router = require('koa-router')
const router = new Router({ prefix: '/address' })
const { auth } = require('../middleware/auth.middleware')
const { validator } = require('../middleware/address.middleware')
const { create } = require('../controller/address.controller')
router.post('/', auth, validator({
consignee: 'string',
// 添加正则验证
phone: { type: 'string', format: /^1\d{10}$/ },
address: 'string'
}), create)
module.exports = router
address/address.controller.js
const { createAddress } = require('../service/address.service')
class AddController {
async create(ctx) {
const userId = ctx.state.user.id
const { consignee, phone, address } = ctx.request.body
const res = await createAddress({ userId, consignee, phone, address })
ctx.body = {
code: 0,
message: '添加地址成功',
result: res
}
}
}
module.exports = new AddController()
service/address.service.js
const Address = require('../model/address.model')
class AddressService {
async createAddress(address) {
return await Address.create(address)
}
}
module.exports = new AddressService()
model/address.model.js
const { DataTypes } = require('sequelize')
const seq = require('../db/seq')
const Address = seq.define('addresses', {
userId: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '用户id'
},
consignee: {
type: DataTypes.STRING,
allowNull: false,
comment: '收货人的手机号',
},
address: {
type: DataTypes.STRING,
allowNull: false,
comment: '收货人的地址',
},
isDefault: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
comment: '是否为默认地址,0:不是(默认值) 1:是'
}
})
// 同步, sync
Address.sync({ force: false })
// 导出模型对象
module.exports = Address
除了这个正则,其余的都是老内容
30、获取、更新、删除地址和设置地址默认值
因为比较简单就写在一起
router/address.route.js
// 获取地址列表
router.get('/', auth, findAll)
// 修改地址数据
router.put('/:id', auth, validator({
consignee: 'string',
phone: { type: 'string', format: /^1\d{10}$/ },
address: 'string'
}), update)
// 删除地址
router.delete('/:id', auth, remove)
// 设置默认
router.patch('/:id', auth, setDefault)
controller/address.controller.js
async findAll(ctx) {
const userId = ctx.state.user.id
const res = await findAllAddress(userId)
ctx.body = {
code: 0,
message: '获取列表成功',
result: res
}
}
async update(ctx) {
const id = ctx.request.params.id
const res = await updateAddress(id, ctx.request.body)
ctx.body = {
code: 0,
message: '更新地址成功',
result: res
}
}
async remove(ctx) {
const id = ctx.request.params.id
const res = await removeAddress(id)
ctx.body = {
code: 0,
message: '删除地址成功',
result: res
}
}
async setDefault(ctx) {
const id = ctx.request.params.id
const userId = ctx.state.user.id
const res = await setDefaultAddress(userId, id)
ctx.body = {
code: 0,
message: '设置默认成功',
result: res
}
}
service/address.service.js
async findAllAddress(userId) {
return await Address.findAll({
attributes: ['id', 'consignee', 'address', 'isDefault'],
where: { userId }
})
}
async updateAddress(id, addressData) {
return await Address.update(addressData, { where: { id } })
}
async removeAddress(id) {
return await Address.destroy({ where: { id } })
}
async setDefaultAddress(userId, id) {
console.log(111, userId, id);
// 将其他的地址默认值都设为false,为传过来的id铺路
await Address.update(
{ isDefault: false },
{
where: {
userId
}
}
)
// 根据传过来的地址id,将地址设为默认值
return await Address.update(
{ isDefault: true },
{
where: {
id
}
}
)
}