npm init -y
生成package.json文件:
-记录项目的依赖
git init
生成‘.git’隐藏文件夹,git的本地仓库
npm install koa
创建src/main.js
const Koa=require('koa')
// 创建服务
const app=new Koa()
// 中间件
app.use((ctx,next)=>{
ctx.body='hello world!'
})
app.listen(3000,()=>{
console.log('sever is running on http://localhost:3000');
})
在终端,使用node src/main.js
安装nodeman工具
npm i nodemon -D
编写package.json脚本
"scripts": {
"dev":"nodemon ./src/main.js"
},
执行npm run dev
启动服务
安装 dotenv
,读取跟目录中的.env文件,将配置写在process.env中
npm i dotenv
创建.env文件
APP_PORT=3000
创建 src/config/config.default.js
const dotenv=require('dotenv')
dotenv.config()
console.log(process.env.APP_PORT);
module.exports=process.env
改写main.js
const Koa=require('koa')
const {APP_PORT}=require('./config/config.default')
// 创建服务
const app=new Koa()
// 中间件
app.use((ctx,next)=>{
ctx.body='hello world'
})
app.listen(3000,()=>{
console.log(`sever is running on http://localhost:${APP_PORT}`);
})
路由:根据不同的URL,调用对应的处理函数
npm i koa-router
步骤:
- 导入包
- 实例化对象
- 编写路由
- 注册中间件
创建src/router
目录,编写user.route.js
const Router=require('koa-router')
const router=Router({prefix:'/users'})
// GET /users/(统一前缀)
router.get('/',(ctx,next)=>{
ctx.body='hello user'
})
module.exports=router
const app=new Koa()
const userRouter=require('./router/user.route')
app.use(userRouter.routes())
创建src/app
const Koa=require('koa')
const app=new Koa()
const userRouter=require('../router/user.route')
app.use(userRouter.routes())
module.exports=app
改写main.js
const app=require('./app')
const {APP_PORT}=require('./config/config.default')
app.use((ctx,next)=>{
ctx.body='hello world'
})
app.listen(3000,()=>{
console.log(`sever is running on http://localhost:${APP_PORT}`);
})
路由:解析URL,分布给控制器对应的方法
控制器:处理不同的业务
改写user.route.js
const Router=require('koa-router')
const {register,login}=require('../controller/user.controller')
const router=Router({prefix:'/users'})
// 注册接口
router.post('/register',register)
// 登录接口
router.post('/login',login )
module.exports=router
创建controller/user.controller.js
class UserController{
async register(ctx,next){
ctx.body='用户注册成功'
}
async login(ctx,next){
ctx.body='用户登录成功'
}
}
module.exports=new UserController()
npm i koa-body
改写app/index.js
改写user.controller.js
const {createUser}=require('../service/user.service')
class UserController{
async register(ctx,next){
// 1.获取数据
const {user_name,password}=ctx.request.body
// 2.操作数据库
const res=await createUser(user_name,password)
// 3.返回结果
ctx.body=res
}
async login(ctx,next){
ctx.body='用户登录成功'
}
}
module.exports=new UserController()
service层主要是做数据库处理
创建src/service/user.service.js
class UserService{
async createUser(user_name,password){
// todo:写入数据库
return '写入数据库成功'
}
}
module.exports=new UserService()
sequelize ORM数据库工具
ORM:对象关系映射
- 数据表映射(对应)一个类
- 数据表中的数据行(记录)对应一个对象
- 数据表字段对应对象的属性
- 数据表的操作对应对象的方法
详细用法参考:sequelize官方文档](https://www.sequelize.cn/)
npm i mysql2 sequelize
src/db/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((err)=>{
// console.log('数据库连接失败');
// })
// 导出连接对象
module.exports=seq
APP_PORT=3000
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PWD=123456
MYSQL_DB=zdsc
sequelize主要通过Model对应数据表
创建src/model/user.model.js
const { DataTypes } = require('sequelize')
const seq = require('../db/seq')
// 创建模型
const User = seq.define('zd_ser', {
// 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: '是否为管理员'
},
},
)
// User.sync({ force: true })强制删除原表然后重建
module.exports=User
所有数据库的操作都在Service
层完成,Service
调用Model
完成数据库操作
改写user.service.js
const User=require('../model/user.model')
class UserService{
async createUser(user_name,password){
// todo:写入数据库
// await表达式:promise对象
const res= await User.create({user_name,password})//简写
// console.log(res);
return res.dataValues
}
}
module.exports=new UserService()
同时,改写user.controller.js
中的方法 6
async register(ctx,next){
// 1.获取数据
const {user_name,password}=ctx.request.body
// 2.操作数据库
const res=await createUser(user_name,password)
// 3.返回结果
ctx.body={
code:0,
message:'用户注册成功',
result:{
id:res.id,
user_name:res.user_name
}
}
}
1 改写user.controller.js
,在获取数据后添加校验代码
// 1.获取数据
const {user_name,password}=ctx.request.body
// 合法性
if(!user_name||!password){
console.error('用户名或密码为空');
ctx.status=400,
ctx.body={
code:'10001',
message:'用户名或密码为空',
result:'',
}
return
}
// 合理性
if(getUserInfo({user_name})){
ctx.status=409,
ctx.body={
code:'10002',
message:'用户名已存在',
result:""
}
return
}
2 改写user.service.js
,添加对应方法
async getUserInfo({id,user_name,password,is_admin}){
const whereOpt={}
// 若id存在就赋值给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','where','password','is_admin'],
where:whereOpt
})
return res?res.dataValues:null
}
在错误处理
的基础上优化代码结构
建立constants和errHandler.js
constants:存放各种错误返回类型
- err.type.js(constants文件夹下):
// 定义错误类型
module.exports={
userFormatErr:{
code:'10001',
message:'用户名或密码为空',
result:''
},
userExitErr:{
code:'10002',
message:'用户已经存在',
result:''
},
userRegisterErr:{
code:'10003',
message:'用户注册错误',
result:''
}
}
- errHandler.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
}
在user.middleware.js
中提交错误处理
ctx.app.emit('error',userExitErr,ctx)
在app/index.js
中统一接收错误处理事件
const errHandler=require('./errHandler')
app.on('error',errHandler)
某些错误处理事件会发生独立性错误,需采用try-catch写法
const verifyUser=async(ctx,next)=>{
const {user_name}=ctx.request.body
try{
const res=await getUserInfo({user_name})
if(res){
console.error('用户名已存在',{user_name});
ctx.app.emit('error',userExitErr,ctx)
return//阻止下一个中间件执行
}
}catch(err){
console.error('获取用户信息错误',err);
ctx.app.emit('error',userRegisterErr,ctx)
return
}
await next()
}
将密码保存到数据库前,对其进行加密
npm install bcryptjs
const crpyPassword=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 {userValidator,verifyUser,crpyPassword}=require('../middleware/user.middleware')
router.post('/register',userValidator,verifyUser,crpyPassword,register)
阶段总结:
- 登录校验中间件
const verifyLogin=async(ctx,next)=>{
const {user_name,password}=ctx.request.body
try{
const res=await getUserInfo({user_name})
if(!res){
console.error('用户不存在',user_name);
ctx.app.emit('error',userNotExitErr,ctx)
return
}
if(!bcrypt.compareSync(password, res.password)){
ctx.app.emit('error',invalidPwdErr,ctx)
return
}
}catch(err){
console.error('用户登录失败',err);
ctx.app.emit('error',userLoginErr,ctx)
return
}
await next()
}
- 登录处理函数
async login(ctx,next) {
const {user_name,password}=ctx.request.body
// ctx.body=`用户登陆成功,${user_name}`
ctx.body={
code:0,
message:'用户登录成功',
result:{
user_name
}
}
}
登录成功后颁发token
jwt:jsonwebtoken
- header:头部
- payload:负载
- signiture:签名
npm install jsonwebtoken
async login(ctx,next) {
const {user_name,password}=ctx.request.body
// token的payload需要获取用户信息
try{
// 获取除密码之外的所有信息
const {password,...restUser}=await getUserInfo({user_name})
ctx.body={
code:0,
message:'用户登录成功',
result:{
token:jwt.sign(restUser,JWT_SECRET,{expiresIn:'2d'}),
}
}
}catch(err){
console.error('用户登陆失败',err);
}
}
const jwt=require('jsonwebtoken')
const {JWT_SECRET}=require('../config/config.default')
const {tokenExpiresErr,invalidTokenErr}=require('../constants/err.type')
const auth=async(ctx,next)=>{
// 获取token
const {authorization}=ctx.request.header
const token=authorization.replace('Bearer ','')
// console.log(token);
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',tokenExpiresErr,ctx)
case 'JsonWebTokenError':
console.error('无效的token',invalidTokenErr);
return ctx.app.emit('error',invalidTokenErr,ctx)
}
}
await next()
}
module.exports={
auth
}
index.js
const fs=require('fs')
const Router=require('koa-router')
const router=new Router()
fs.readdirSync(__dirname).forEach(file=>{
// console.log(file);
if(file!='index.js'){
let r=require('./'+file)
router.use(r.routes())
}
})
// 导出
module.exports=router
PS:allowedMethods()
用于限制请求发送类型
const router=require('../router/index')
app.use(router.routes()).use(router.allowedMethods())
在auth.middleware.js
中添加授权中间件
const hasAdminPermission=async(ctx,next)=>{
const {is_admin}=ctx.state.user
if(!is_admin){
console.error('该用户没有管理员权限');
return ctx.app.emit('error',noAdminPermissionErr,ctx)
}
await next()
}
app.use(koaBody({
// 以下为上传图片的需要声明的option
multipart:true,
formidable:{
// 在option中尽量使用绝对路径
uploadDir:path.join(__dirname,'../upload'),
keepExtensions:true
}
}))
async upload(ctx,next){
const {file}=ctx.request.files
console.log(file);
if(file){
console.log('yeahhh');
ctx.body={
code:0,
message:'商品图片上传成功',
result:{
goods_img:path.basename(file.filepath),
}
}
}else{
return ctx.app.emit('error',uploadImgErr,ctx)
}
}
-
route
层router.post('/',auth,hasAdminPermission,validator,publishGoods)
-
controller
层async publishGoods(ctx,next){ // 调用sevice try{ const {updatedAt,createdAt,...res}=await createGood(ctx.request.body) ctx.body={ code:0, message:'商品发布成功', result:res } }catch(err){ console.error(err); return ctx.app.emit('error',goodPubErr,ctx) } }
-
service
层async createGood(good){ const res=await Goods.create(good) console.log('创建商品成功'); return res.dataValues }
-
route
层router.put('/:id',auth,hasAdminPermission,validator,updateGoods)
-
controller
层async updateGoods(ctx){ try{ const res=await updateGood(ctx.request.body,ctx.params.id) ctx.body={ code:0, message:'修改成功', result:'' } }catch(err){ console.error(err); return ctx.app.emit('error',goodUpdateErr,ctx) } }
-
service
层async updateGood(good,id){ const res=await Goods.update(good,{where:{id}}) return res[0]>0?true:false }
-
route
层router.post('/:id/off',auth,hasAdminPermission,remove)
-
controller
层async remove(ctx){ const res=await reomveGoods(ctx.params.id) if(res){ ctx.body={ code:0, message:'下架成功', result:'' } }else{ return ctx.app.emit('error',goodRemoveErr,ctx) } }
-
service
层async reomveGoods(id){ const res=await Goods.destroy({where:{id}}) return res==1?true:false }
-
route
层router.get('/',findAll)
-
controller
层async findAll(ctx){ // 1.解析pageNum和pageSize const {pageNum=1,pageSize=10}=ctx.request.query // 2.调用数据处理的相关方法 try{ const res=await getGoods(pageNum,pageSize) // 3.返回结果 ctx.body={ code:0, message:'获取商品成功', result:res } }catch(err){ console.error(err); return ctx.app.emit('error',goodGetErr,ctx) } }
-
service
层async getGoods(pageNum,pageSize){ const {count,rows}=await Goods.findAndCountAll({ offset:(pageNum-1)*pageSize, limit:pageSize*1 }) return{ pageNum, pageSize, total:count, list:rows } }