记录后端服务(koa2)

149 阅读4分钟

后端服务

选择后端框架( 我选择的是koa2)

创建文件夹

backend/
├── src/
│   ├── app/               # web app本身
│   │   ├── index.js        
│   ├── controllers/       # 控制器,处理路由请求
│   │   ├── test.js        
│   ├── middleware/        # 中间件
│   │   ├── test.js        
│   ├── models/            # 数据模型(Mongoose)
│   │   ├── test.js            
│   ├── routes/            # 路由定义
│   │   ├── index.js       # 路由
│   │   ├── test.js            
│   ├── services/          # 业务逻辑
│   │   ├── test.js           
│   ├── utils/             # 工具函数
│   │   ├── db.js          # 数据库连接
│   │   ├── globalVariables.js          #  导入.env 全局变量
│   └── main.js            # 启动服务入口文件
└─——.env                   # 环境变量
├── .gitignore             # git上传配置
└── package.json           # 项目依赖


安装

npm install koa //安装koa
npm i nodemon -D //自动重启服务
npm install dotenv //全局变量配置
npm install koa-logger // 监控中间件
npm install koa-route  // 路由中间件
npm install koa-parameter // 用来校验请求传送过来的参数是否是自己所需要的
npm install koa-cors //解决跨越
npm install mongoose //连接数据库

配置

在app/index.js

const Koa = require('koa');
const Cors = require("koa-cors");
const parameter = require('koa-parameter')
const router = require('../routes')

const app = new Koa(); // 创建koa实例 

app.use(Cors()); // 使用cors中间件 处理跨域
app.use(parameter(app)) // 使用parameter中间件 处理请求参数
app.use(router.routes()).use(router.allowedMethods());// 定义路由中间件

module.exports = app; // 导出app实例

在routes/index

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 //导出

在routes/test.js

const Router = require('koa-router')

const router = new Router({ prefix: "/MyTest" }); // 实例化路由

router.get('/', async (ctx) => { // 定义路由
  ctx.body = 'Hello World!';
})

module.exports = router // 导出路由

在.env文件

APP_PORT = 8000 #端口号

#数据库配置
MONGO_URI = mongodb://127.0.0.1:27017/  #数据库地址
MONGO_DB_NAME = xx #数据库名称
MONGO_USERNAME = xx #数据库用户名
MONGO_PASSWORD = xx #数据库密码

# 密码加密配置
JWT_SECRET = xx # 密钥
REFRESH_TOKEN_SECRET = xx # 刷新密钥
JWT_EXPIRES_IN = 7d # token过期时间
REFRESH_TOKEN_EXPIRES_IN = 30d # 刷新token过期时间

在utils/globalVariables

const dotenv = require('dotenv')

dotenv.config()

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

module.exports = process.env

在main.js中导入

const { APP_PORT } = require('./utils/globalVariables') //导入端口号

const app = require('./app') //导入app实例

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

简单的服务已经完成,浏览器访问localhost:8000/MyTest/会看到'Hello World!'

业务

  1. 连接数据库
  2. 设计数据表模型(models
  3. 写路由 (router
  4. 写中间件(middleware)和控制器(controller)
  5. 处理业务逻辑(service
  6. 利用模型实现业务

连接数据库

我这里使用的是 mongoose 连接 MongoDB,也可以使用其他的例如 sequelize连接 MySQL,要记得把数据库建好

在utils/db.js文件夹下

const mongoose = require('mongoose');

const url = process.env.MONGO_URI || 'mongodb://localhost:27017/';
const optins = {
  dbName: process.env.MONGO_DB_NAME,// 数据库名称
}

// 连接数据库
const connectDB = async () => {
  try {
    await mongoose.connect(url, optins);
    console.log('数据库连接成功');
  } catch (err) {
    console.error(err.message);
    // Exit process with failure
    process.exit(1);
  }
};

module.exports = connectDB;

main.js

const { APP_PORT } = require('./utils/globalVariables') //导入端口号
const connectDB = require('./utils/db')

const app = require('./app') //导入app实例

//连接数据库
connectDB().then(()=>{
  app.listen(APP_PORT, () => { //监听端口
    console.log(`server is running on http://localhost:${APP_PORT}`)
  })
})

创建数据模型

models 文件夹下创建user.js

// models/user.js
const moogoose = require('mongoose');

const UserSchema = new moogoose.Schema({
  name: {
    type: String,
    required: true
  },
  password: {
    type: String,
    required: true
  },
  role: {
    type: String,
    required: true,
    enum: ['admin', 'user', 'guest',],//限定role的值只能是admin、user、guest
    default: 'user',
    comment: 'admin(管理员), user(用户), guest (访客)'
  }
})

编写路由

在routes文件夹下创建user.js

// routes/user.js
const Router = require("koa-router"); //

const router = new Router({ prefix: "/user" });

const {checkEmpty,checkUser,crpytPassword,checkLogin,auth} = require("../middleware/User");

const {register,login,updatePassword,updateToken} = require("../controllers/User");
// 注册
router.post("/register",checkEmpty,checkUser,crpytPassword,register)

// 登录
router.post("/login",checkEmpty,checkLogin,login);


// 修改用户信息
router.put("/updatePassword",auth,checkLogin, updatePassword);


// 刷新token
router.post("/refreshToken",updateToken);

module.exports = router;

编写中间件(middleware)与控制器(controllers)

在middleware和controllers文件夹下创建User.js

// middleware/User.js
const jwt = require('jsonwebtoken')
const User = require('../models/user')
const { hashPassword, comparePassword } = require('../utils/bcrpyt')

const { userFormateError, userAlreadyExited, userDoesNotExist, invalidPassword,tokenExpiredError } = require('../constant/errorType')

// 验证用户名或密码是否空
const checkEmpty = async (ctx, next) => {
  try {
    const { username, password } = ctx.request.body
    if (!username || !password) {
      console.error('用户名或密码为空', ctx.request.body)
      ctx.app.emit('error', userFormateError, ctx)
      return
    }
    await next()
  } catch (error) {
    console.error("验证用户名或密码是否空", error);
  }
}

// 验证用户名是否已经存在
const checkUser = async (ctx, next) => {
  try {
    const { username } = ctx.request.body
    const user = await User.findOne({ username })
    if (user) {
      console.error('用户名已存在', ctx.request.body)
      ctx.app.emit('error', userAlreadyExited, ctx)
      return
    }
    await next()
  } catch (error) {
    console.error("验证用户名是否存在", error);
  }
}
// 密码加加密
const crpytPassword = async (ctx, next) => {
  const { password } = ctx.request.body

  const hash = await hashPassword(password)//加密

  ctx.request.body.password = hash

  await next()
}

//验证用户是否注册,密码是否正确
const checkLogin = async (ctx, next) => {
  try {
    const { username, password } = ctx.request.body;
    const user = await User.findOne({ username })
    if (!user || !(await comparePassword(password, user.password))) {
      console.error('验证用户是否注册,密码是否正确', ctx.request.body)
      ctx.app.emit('error', !user ? userDoesNotExist : invalidPassword, ctx)
      return
    }

    await next()
  } catch (error) {
    console.log("验证用户是否注册", error);
  }
}

// 验证用户登录是否过期
const auth = async (ctx, next) => {
  try {
    const token = ctx.headers.authorization?.split(' ')[1];
    const user = jwt.verify(token, process.env.JWT_SECRET);
    ctx.state.user = user;
    await next();
  } catch (error) {
    console.log("验证用户登录是否过期", error);
    ctx.app.emit('error',tokenExpiredError,ctx )
  }
}

module.exports = {
  checkEmpty,
  checkUser,
  crpytPassword,
  checkLogin,
  auth
}
// controllers/User.js
const { userRegisterError, userLoginError, invalidPassword,invalidToken } = require('../constant/errorType')
const { createtUser, getUser,changPassword,changToken } = require('../services/User')
// 注册
const register = async (ctx, next) => {
    try {
        const { username, password } = ctx.request.body;
        const user = await createtUser(username, password);
        ctx.body = {
            code: 200,
            message: '注册成功',
            result: user
        };
    } catch (error) {
        console.log("注册", error)
        ctx.app.emit('error', userRegisterError, ctx)
    }
}

// 登录
const login = async (ctx, next) => {
    try {
        const { username, password } = ctx.request.body;
        const userinfo = await getUser(username, password)
        ctx.body = {
            code: 200,
            message: '登录成功',
            result: userinfo
        }
    } catch (error) {
        console.log("登录", error);
        ctx.app.emit('error', userLoginError, ctx)
    }
}

// 修改密码
const updatePassword = async (ctx, next) => {
    try {
        const {id, username, newPassword } = ctx.request.body;
        await changPassword(id,username,newPassword)
        ctx.body = {
            code: 200,
            message: '修改密码成功',
        }
    }catch (error) {
        console.log("修改密码", error);
        ctx.app.emit('error', invalidPassword, ctx)
    }

}

//刷新token
const updateToken= async (ctx, next) => {
    try {
       const { refreshToken } = ctx.request.body;
       const token= await changToken(refreshToken)
        ctx.body = {
            code: 200,
            message: '刷新token成功',
            token
        }
    } catch (error) {
        console.log("刷新token", error);
        ctx.app.emit('error', invalidToken, ctx)
    }
}

module.exports = {
    register,
    login,
    updatePassword,
    updateToken
}

处理业务逻辑

在services文件夹下创建User.js

//services/User.js
const User = require('../models/user')
const { generateToken, generateRefreshToken,verifyToken } = require('../utils/jwt')
const { hashPassword } = require('../utils/bcrpyt')

// 创建用户
const createtUser = async (username, password) => {
  const user = new User({ username, password });
  await user.save(); // 保存用户
  return user;
}

// 用户登录
const getUser = async (username) => {
  const user = await User.findOne({ username }); // 查询用户

  const token = generateToken(user) // 生成token
  const refreshToken = generateRefreshToken(user); // 生成刷新token
  // 将refreshToken存入数据库
  user.refreshToken = refreshToken;
  await user.save();

  const userinfo = {
    token,
    refreshToken,
    user
  }
  return userinfo

}

//更新密码
const changPassword = async (id, username, newPassword) => {
  const password = await hashPassword(newPassword)//加密
  await User.findByIdAndUpdate({ _id: id }, { password });
}

// 刷新token
const changToken = async (refreshToken) => {
  if (!refreshToken) {
    ctx.status = 400;
    ctx.body = { error: 'refreshToken不能为空' };
    return;
  }
  const decoded = verifyToken(refreshToken, process.env.REFRESH_TOKEN_SECRET);//验证token
  const user = await User.findById(decoded.id);//查询用户
  if (!user || user.refreshToken !== refreshToken) {
    ctx.status = 403;
    ctx.body = { error: '无效或过期的refreshToken' };
    return;
  }

  const newToken = generateToken(user);     // 生成新的token
  const newRefreshToken = generateRefreshToken(user); // 生成刷新token
  user.refreshToken = newRefreshToken;
  await user.save(); // 保存用户刷新token

  let token = {
    token: newToken,
    refreshToken: newRefreshToken,
  }
  return token
}


module.exports = {
  createtUser,
  getUser,
  changPassword,
  changToken
}

项目链接 vue3: 后台 koa2 + 前端 vue3+vite 的简单模板 (gitee.com)