一. 项目的初始化
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
步骤:
- 导入包
- 实例化对象
- 编写路由
- 注册中间件
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;
-
用 control 管理返回
jsclass 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';
}
}