从0开始用node.js koa创建博客登录注册篇

135 阅读3分钟

前言:用nodejs写一个前后端分离的入门级别博客。也是记录自己在这个平台第一次写文章。 这个项目分为登录注册篇、内容管理篇、评论管理篇以及去怎样部署一个node项目。

开始:1、创建目录--分层架构

image.png

image.png

2、因为我们的账户密码是私密的,部署到线上是不能给别人知道的。所以要写到另外文件上 比如我写在.env文件上

APP_PORT = 8082
MYSQL_HOST = 'localhost'
MYSQL_PORT = 3306
MYSQL_USER = 'root'
MYSQL_PASSWORD = '123456'
MYSQL_DATABASE = 'myblog'
JWT_SECRET_KEY = 'xxxx_666_xxxx...'
JWT_EXPIRE_TIME = 86400

3.读取配置代码config.js

const dotdev = require('dotenv'); // 导入 dotenv 模块解析配置文件
dotdev.config(); // 读取配置文件
// 导出配置信息
module.exports = {
  APP_PORT,
  MYSQL_HOST,
  MYSQL_PORT,
  MYSQL_DATABASE,
  MYSQL_USER,
  MYSQL_PASSWORD,
  JWT_SECRET_KEY,
  JWT_EXPIRE_TIME,
} = process.env


4.写好配置文件后在main.js入口文件引入koa库以及路由和其他中间件

const koa = require('koa')
const app = new koa()
require("./app/database") // 导入数据库连接
const bodyParser = require('koa-bodyparser'); // 解析请求体
const config = require("./app/config") // 导入配置
const userRoutes = require('./router') // 导入路由
app.use(bodyParser()) // 注册body-parser中间件
userRoutes(app) // 注册路由


// 启动服务
app.listen(config.APP_PORT, () => {
  console.log(config.APP_PORT)
})

5、数据库代码编写database.js

const mysql2 = require('mysql2');
const config = require('./config');
const pool = mysql2.createPool({
  host: config.MYSQL_HOST,
  port: config.MYSQL_PORT,
  user: config.MYSQL_USER,
  password: config.MYSQL_PASSWORD,
  database: config.MYSQL_DATABASE,
  connectionLimit: 10,
});
// 测试连接
pool.getConnection((err, connection) => {
  connection.connect(err=>{
    if(err){
      console.error('Error connecting to database: ', err);
    }else{
      console.log('Connected to database');
    }
  });
  
})
module.exports = pool.promise();

6.因为项目的路由比较多,所以采用动态路由读取。index.js

const fs = require('fs');
// 动态导入路由文件
const userRoutes = (app) => {
  fs.readdirSync(__dirname).forEach((file) => {
    if (file.endsWith('.js') && file!== 'index.js') {
      const router = require(`./${file}`);
      app.use(router.routes());
      app.use(router.allowedMethods());
    }
  });
}

module.exports = userRoutes;

7、用户相关路由 userRouter.js

const Router = require('koa-router')
//  用户相关控制器
const {
  createUser,
  userLogin
} = require('../controller/userController')
// 用户相关中间件
const {
  verifyUser,
  verifyLogin,
  verifyToken
} = require('../middleware/userMiddleware')
const router = new Router()
router.prefix('/user')

// 用户相关路由注册和登录
router.post('/create',verifyUser,createUser)
router.post('/login',verifyLogin,userLogin)
router.get('/test',verifyToken, async (ctx,next) => {
  ctx.body = 'test'
})
module.exports = router

8、用户相关控制器 userController.js

const service = require('../services/userService');
const jwt = require('jsonwebtoken');
const config = require('../app/config');
class UserController {
  // 创建用户
  async createUser(ctx, next) {
    // 获取用户请求参数
    const user= ctx.request.body;
    const result = await service.create(user);
    ctx.body = result
  }
  // 用户登录
  async userLogin(ctx, next) {
    const {id, username} = ctx.user;
    // 登录成功返回token
    const token = jwt.sign({id, username}, 
      config.JWT_SECRET_KEY, 
      {expiresIn: config.JWT_EXPIRE_TIME, algorithm: 'HS256'});
      // 返回token
    ctx.body = {
      code: 200,
      id,
      username,
      token,
      message: `登录成功, 用户名为:${username}`
    };
  }
}

module.exports = new UserController;


9、控制器和逻辑分离目录,这样看起来比较清晰,在userService.js编写

const db = require('../app/database')
const { getDate,encryptPassword } = require('../utils/utils')
class UserService {
  // 创建用户
  async create(user) {
    // 将用户保存到数据库中
    let { username, password } = user
    // 密码加密处理
    password = encryptPassword(password)
    // 获取当前时间
    const create_time = getDate()
    const sql = `INSERT INTO users (username, password,create_time) values (?,?,?)`
    const result = await db.execute(sql, [username, password, create_time])
    return result
  }
  // 根据用户名查找用户
  async findByName(username) {
    const sql = `SELECT * FROM users WHERE username = ?`
    const result = await db.query(sql, [username])
    return result[0]
  }
  // // 登录
  // async login(user) {
  //   let { username, password } = user
  //   password = encryptPassword(password)
  //   const sql = `SELECT * FROM users WHERE username = ?`
  //   const result = await db.query(sql, [username, password])
  //   return result[0]
  // }
}

module.exports = new UserService();

10、判断用户名以及密码是否为空,在中间件编写userMiddleware.js

const service = require('../services/userService')
const { encryptPassword } = require('../utils/utils')
const jwt = require('jsonwebtoken')
const config = require('../app/config');
// 注册验证中间件
const verifyUser = async (ctx, next)=>{
  // 获取用户名和密码
  const {username ,password} = ctx.request.body
  // 判断用户名或者密码不能为空
  if(!username || !password){
    ctx.status = 400
    ctx.body = {
      code:"400",
      message: '用户名或密码不能为空'
    }
    return
  }
  // 判断用户名是否存在
  const result = await service.findByName(username)
  if (result.length > 0) {
    ctx.status = 400
    ctx.body = {
      code:"400",
      message: '用户已存在'
    }
    return
  }
  // code to verify user
  await next()
}
// 登录验证中间件
const verifyLogin = async (ctx, next) => {
  // 获取用户名和密码
  let { username, password } = ctx.request.body
  // 判断用户名或者密码不能为空
  if (!username || !password) {
    ctx.status = 400
    ctx.body = {
      code:"400",
      message: '用户名或密码不能为空'
    }
    return
  }
  // 判断用户名是否存在
  const result = await service.findByName(username)
  if (result.length === 0) {
    ctx.status = 400
    ctx.body = {
      code:"400",
      message: '用户不存在'
    }
    return
  }
  // 判断密码是否正确
  const isMath = encryptPassword(password) === result[0].password
  if (!isMath) {
    ctx.status = 400
    ctx.body = {
      code:"400",
      message: '密码错误'
    }
    return
  }
  ctx.user = result[0]
  await next()
}
// 授权中间件
const verifyToken = async (ctx, next) => {
  const authorization = ctx.headers.authorization
  const token = authorization.replace('Bearer ', '')
  if (!token) {
    ctx.status = 401
    ctx.body = {
      code:"401",
      message: '请登录'
    }
    return
  }
  try {
    const decoded = jwt.verify(token,
       config.JWT_SECRET_KEY,
      { algorithms: ['HS256']})
    ctx.user = decoded
    await next()
  } catch (error) {
    ctx.status = 401
    ctx.body = {
      code:"401",
      message: "令牌错误或已过期"
    }
  }
}
module.exports = {
  verifyUser,
  verifyLogin,
  verifyToken
}

11、工具函数 utils目录下编写

const crypto = require('crypto');

//  获取当前日期
function getDate(){
  return new Date();
}
//  密码加密
function encryptPassword(password){
  const result = crypto.createHash('md5').update(password).digest('hex');
  return result;
}
module.exports = {
  getDate,
  encryptPassword
};

先这样,测试了 注册 :成功的返回一个insertId

image.png 判断是否注册

image.png

登录成功后返回用户信息和token

image.png 登录用户名不存在

image.png 登录密码错误

image.png

ps:如有写的不好,请指正,共勉。谢谢!会持续更新。