配置一个相对完整的Koa脚手架

一个相对完整的Koa项目结构通常包括以下文件夹和文件:

config:存放配置文件,如数据库连接信息、日志配置等。
controllers:存放控制器文件,处理业务逻辑。
middlewares:存放中间件文件,用于处理请求、错误处理、日志记录等。
models:存放数据模型文件,用于定义数据结构和数据库交互。
routes:存放路由文件,定义API端点和路由处理逻辑。
services:存放服务文件,封装业务逻辑和数据访问。
utils:存放工具函数和通用功能的文件。
logs:存放日志文件,用于记录应用程序的运行日志。
app.js:应用程序的入口文件,用于创建Koa实例、加载中间件和路由等。
package.json:管理项目的依赖和脚本。
.env:存放环境变量配置。

erlang 复制代码
project/
├── config/
│   ├── db.js
│   ├── logger.js
│   └── ...
├── controllers/
│   ├── authController.js
│   └── ...
├── middlewares/
│   ├── errorHandler.js
│   ├── logger.js
│   └── ...
├── models/
│   ├── user.js
│   └── ...
├── routes/
│   ├── authRoutes.js
│   └── ...
├── services/
│   ├── authService.js
│   └── ...
├── utils/
│   ├── validation.js
│   └── ...
├── logs/
│   ├── error.log
│   ├── info.log
│   └── ...
├── app.js
├── package.json
├── .env

跨域配置

javascript 复制代码
app.use(async (ctx, next) => {
  ctx.set('Access-Control-Allow-Credentials', 'true'); // 允许发送 Cookie
  ctx.set('Access-Control-Allow-Origin', ctx.request.header.origin); // 允许发送 Cookie
  ctx.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); // 允许的请求方法
  ctx.set('Access-Control-Allow-Headers', 'Content-Type');
  ctx.set('Content-Type', 'application/json;charset=utf-8');
    // 对于预检请求(OPTIONS请求),直接返回200状态码
  if (ctx.method === 'OPTIONS') {
    ctx.status=204;
    ctx.body=""
  } else {
    await next();
  }
});

中间件

koa-bodyparser

解析请求体并将其存储在ctx.request.body属性中

javascript 复制代码
pnpm i koa-bodyparser
javascript 复制代码
const onerror = require('koa-bodyparser')
onerror(app)

koa-json-error

将响应体转换为JSON格式,并设置适当的Content-Type头

javascript 复制代码
pnpm i koa-json-error
javascript 复制代码
const onerror = require('koa-json-error')
app.use(onerror({
  format: (err) => { // 返回错误的格式
    return { code: err.status, message: err.message, result: err.stack }
  },
  postFormat: (e, { stack, ...rest }) => process.env.NODE_ENV === 'production' ? rest : { stack, ...rest },
}))

log4js

日志记录

javascript 复制代码
pnpm i log4js
javascript 复制代码
const log4js = require('log4js');
log4js.configure({
  // 定义日志的输出位置,可以包括控制台输出和文件输出。可以根据日志级别分别配置不同的文件输出
  appenders: {
    consoleOut: {
      type: 'console',
      layout: {
        type: 'pattern',
        pattern: '%d{yyyy-MM-dd hh:mm:ss} [%p] %m'
      },
    },
    fileOut: {
      type: 'file',
      filename: `./logs/logger`,
      pattern: 'yyyy-MM-dd.log',
      layout: {
        type: 'pattern',
        pattern: '%d{yyyy-MM-dd hh:mm:ss} [%p] %m'
      },
      alwaysIncludePattern: true,
    },
    errorOut: {
      type: 'file',
      filename: `./logs/error`,
      pattern: 'yyyy-MM-dd.log',
      layout: {
        type: 'pattern',
        pattern: '%d{yyyy-MM-dd hh:mm:ss} [%p] %m'
      },
      alwaysIncludePattern: true,
    },
  },
  // categories定义日志的策略,可以根据日志级别选择不同的输出方式。
  categories: {
    default: {
      appenders: ['consoleOut', 'fileOut'],
      level: 'info',
    },
    error: {
      appenders: ['consoleOut', 'errorOut'],
      level: 'error',
    },
  },
});

module.exports = {
  info: (content) => {
    let logger = log4js.getLogger('info')
    logger.info(content);
  },
  error: (content) => {
    let logger = log4js.getLogger('error')
    logger.error(content);
  }
};

better-sqlite3

一个轻量级数据库,无需安装额外服务 github.com/WiseLibs/be...

javascript 复制代码
pnpm i better-sqlite3
javascript 复制代码
const Database = require('better-sqlite3');
app.use(async (ctx, next) => {
  ctx.database = new Database('./database/data.db', { verbose: console.log }); // 将db挂在ctx上下文对象的database属性上
  await next();
});

// routes/user.js
router.get('/', jwtMiddleware, async (ctx) => {
  const { getUser } = require('../model/user')
  const result = await getUser(ctx.database)
  ctx.body = { data: result }
})

// model/user.js 得先创建用户表
async function addUser(db, data) {
  const stmt = db.prepare('INSERT INTO users (name, password, create_time) VALUES (?, ?, ?)')
  const info = stmt.run(data.name, data.password, new Date().toLocaleString())
  return info
}

async function getUser(db) {
  const stmt = db.prepare('select * from users')
  const data = stmt.all()
  return data
}

jsonwebtoken

鉴权 www.npmjs.com/package/jso...

我把用户名存入了token并放入cookie中,后续客户端请求设置携带cookie请求,既能鉴权也可直接获取用户信息

javascript 复制代码
pnpm i jsonwebtoken

封装jwt验证中间件

javascript 复制代码
const jwt = require('jsonwebtoken');
const jwtMiddleware = async (ctx, next) => {
  // 从 Cookie 中获取 JWT
  const token = ctx.cookies.get('token');
  
  if (token) {
    try {
      // 验证 JWT
      const decoded = jwt.verify(token, 'secritykey');

      // 将解码后的用户信息添加到请求上下文中
      ctx.state.user = decoded;
    } catch (error) {
      // JWT 验证失败
      ctx.status = 401;
      ctx.body = { code: '-1', message: '未登录' };
      return;
    }
  } else {
    // Cookie 中没有 JWT
    ctx.status = 401;
    ctx.body = { code: '-1', message: '未登录' };
    return;
  }

  await next();
};

module.exports = jwtMiddleware
javascript 复制代码
const jwt = require('jsonwebtoken')

router.get('/login', async (ctx) => {
  const { name } = ctx.request.query
  const token = jwt.sign({ name }, 'secritykey', { algorithm: 'RS256', expiresIn: '1h' }); // 生成JWT令牌,有效期为1小时
  // 将JWT令牌设置为Cookie
  ctx.cookies.set('token', token, { httpOnly: true });
  ctx.body = { data: '1' }
})

router.get('/', async (ctx) => {
  const user = ctx.state.user || {}
  console.log(user);
})
相关推荐
wangruofeng4 小时前
AI Models 1.1.0 发布,查大模型价格、对比模型和找 API 地址
前端·aigc·ai编程
wangruofeng4 小时前
给前端开发者的 100 个设计参考网站
前端·设计
星栈7 小时前
视频即代码,Remotion 能做什么、不能做什么
前端·音视频开发
广州华水科技7 小时前
单北斗GNSS位移监测系统的应用与技术解析
前端
用户47949283569157 小时前
从一句话或代码仓到可交互 HTML:开源 Agent 画图技能 Archify v3.2 实测与选型边界
前端·后端·面试
风骏时光牛马10 小时前
生成一个AI多模态标题,仅输出一条标题,无额外文字、问句、备选、配图、文案等任何附加内容,不需要其他的任务服务 只要标题。
前端
wangruofeng10 小时前
从 GIF 到 AVIF:网页图片格式演进与 2026 年选型指南
前端·架构
清晨Coding11 小时前
为什么用 React Developer Tools 查看组件树这么乱?
前端·react.js
鶴哥只手遮天11 小时前
《Electron 45.2.3 内核解剖》第 4 章 补丁体系:380 个补丁改了什么
前端·electron
FungLeo14 小时前
成为全栈·Vue 管理后台篇·分类树与标签管理:层级数据如何落到管理界面
前端·javascript·vue.js·分类树·naive ui·标签管理·前端数据结构