commit b35897d60bbdb4ca54be724c918da6fb9b57d307 Author: 谢亚昕 <1549469775@qq.com> Date: Mon Jun 16 17:54:36 2025 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b1ee42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..07dae4d --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +msvs_version=2017 \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..b32eb69 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,11 @@ +{ + "tabWidth": 4, + "useTabs": false, + "semi": false, + "singleQuote": false, + "TrailingCooma": "all", + "bracketSpacing": true, + "jsxBracketSameLine": false, + "arrowParens": "avoid", + "printWidth": 140 +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..bfcdd87 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ + +## koa3-demo + +当前支持功能: + + - [ ] 路由 + - [x] 日志 + - [ ] 权限 + - [x] 数据库 + - [ ] 缓存 + - [ ] 界面 + - [ ] 定时任务 \ No newline at end of file diff --git a/bun.lockb b/bun.lockb new file mode 100644 index 0000000..4288996 Binary files /dev/null and b/bun.lockb differ diff --git a/database/development.sqlite3 b/database/development.sqlite3 new file mode 100644 index 0000000..5238449 Binary files /dev/null and b/database/development.sqlite3 differ diff --git a/database/development.sqlite3-shm b/database/development.sqlite3-shm new file mode 100644 index 0000000..2553477 Binary files /dev/null and b/database/development.sqlite3-shm differ diff --git a/database/development.sqlite3-wal b/database/development.sqlite3-wal new file mode 100644 index 0000000..bea45f3 Binary files /dev/null and b/database/development.sqlite3-wal differ diff --git a/knexfile.mjs b/knexfile.mjs new file mode 100644 index 0000000..5c6028c --- /dev/null +++ b/knexfile.mjs @@ -0,0 +1,59 @@ +// knexfile.mjs (ESM格式) +export default { + development: { + client: "sqlite3", + connection: { + filename: "./database/development.sqlite3", + }, + migrations: { + directory: "./src/db/migrations", // 迁移文件目录 + // 启用ES模块支持 + extension: "mjs", + loadExtensions: [".mjs", ".js"], + }, + seeds: { + directory: "./src/db/seeds", // 种子数据目录, + // 启用ES模块支持 + extension: "mjs", + loadExtensions: [".mjs", ".js"], + timestampFilenamePrefix: true, + }, + useNullAsDefault: true, // SQLite需要这一选项 + pool: { + min: 1, + max: 1, // SQLite 建议设为 1,避免并发问题 + afterCreate: (conn, done) => { + conn.run("PRAGMA journal_mode = WAL", done) // 启用 WAL 模式提高并发 + }, + }, + }, + + // 生产环境、测试环境配置可按需添加 + production: { + client: "sqlite3", + connection: { + filename: "./database/db.sqlite3", + }, + migrations: { + directory: "./src/db/migrations", // 迁移文件目录 + // 启用ES模块支持 + extension: "mjs", + loadExtensions: [".mjs", ".js"], + }, + seeds: { + directory: "./src/db/seeds", // 种子数据目录, + // 启用ES模块支持 + extension: "mjs", + loadExtensions: [".mjs", ".js"], + timestampFilenamePrefix: true, + }, + useNullAsDefault: true, // SQLite需要这一选项 + pool: { + min: 1, + max: 1, // SQLite 建议设为 1,避免并发问题 + afterCreate: (conn, done) => { + conn.run("PRAGMA journal_mode = WAL", done) // 启用 WAL 模式提高并发 + }, + }, + }, +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..73ab3df --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "koa3-demo", + "module": "index.js", + "type": "module", + "scripts": { + "dev": "bun --hot src/main.js", + "start": "bun run src/main.js", + "migrate:make": "npx knex migrate:make ", + "migrate": "npx knex migrate:latest", + "seed:make": "npx knex seed:make ", + "seed": "npx knex seed:run " + }, + "devDependencies": { + "@types/bun": "latest", + "@types/node": "^24.0.1", + "knex": "^3.1.0" + }, + "dependencies": { + "koa": "^3.0.0", + "log4js": "^6.9.1", + "module-alias": "^2.2.3", + "sqlite3": "^5.1.7" + }, + "_moduleAliases": { + "@": "./src", + "db": "./src/db" + } +} \ No newline at end of file diff --git a/src/controllers/userController.mjs b/src/controllers/userController.mjs new file mode 100644 index 0000000..5b91a7b --- /dev/null +++ b/src/controllers/userController.mjs @@ -0,0 +1,23 @@ +import db from "../db/index.js" + +// 创建用户 +export async function createUser(userData) { + const [id] = await db("users").insert(userData) + return id +} + +// 查询所有用户 +export async function getUsers() { + return db("users").select("*") +} + +// 更新用户 +export async function updateUser(id, updates) { + updates.updated_at = new Date() + return db("users").where("id", id).update(updates) +} + +// 删除用户 +export async function deleteUser(id) { + return db("users").where("id", id).del() +} diff --git a/src/db/index.js b/src/db/index.js new file mode 100644 index 0000000..8c07589 --- /dev/null +++ b/src/db/index.js @@ -0,0 +1,35 @@ +import buildKnex from "knex" +import knexConfig from "../../knexfile.mjs" + +const environment = process.env.NODE_ENV || 'development'; +const db = buildKnex(knexConfig[environment]); + +export default db; + +// async function createDatabase() { +// try { +// // SQLite会自动创建数据库文件,只需验证连接 +// await db.raw("SELECT 1") +// console.log("SQLite数据库连接成功") + +// // 检查users表是否存在(示例) +// const [tableExists] = await db.raw(` +// SELECT name +// FROM sqlite_master +// WHERE type='table' AND name='users' +// `) + +// if (tableExists) { +// console.log("表 users 已存在") +// } else { +// console.log("表 users 不存在,需要创建(通过迁移)") +// } + +// await db.destroy() +// } catch (error) { +// console.error("数据库操作失败:", error) +// process.exit(1) +// } +// } + +// createDatabase() diff --git a/src/db/migrations/20250616065041_create_users_table.mjs b/src/db/migrations/20250616065041_create_users_table.mjs new file mode 100644 index 0000000..c1f4ca3 --- /dev/null +++ b/src/db/migrations/20250616065041_create_users_table.mjs @@ -0,0 +1,22 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +export const up = async knex => { + return knex.schema.createTable("users", function (table) { + table.increments("id").primary() // 自增主键 + table.string("name", 100).notNullable() // 字符串字段(最大长度100) + table.string("email", 100).unique().notNullable() // 唯一邮箱 + table.integer("age").unsigned() // 无符号整数 + table.timestamp("created_at").defaultTo(knex.fn.now()) // 创建时间 + table.timestamp("updated_at").defaultTo(knex.fn.now()) // 更新时间 + }) +} + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +export const down = async knex => { + return knex.schema.dropTable("users") // 回滚时删除表 +} diff --git a/src/db/models/UserModel.js b/src/db/models/UserModel.js new file mode 100644 index 0000000..f263586 --- /dev/null +++ b/src/db/models/UserModel.js @@ -0,0 +1,26 @@ +import db from "../index.js" + +class UserModel { + static async findAll() { + return db("users").select("*") + } + + static async findById(id) { + return db("users").where("id", id).first() + } + + static async create(data) { + return db("users").insert(data).returning("*") + } + + static async update(id, data) { + return db("users").where("id", id).update(data).returning("*") + } + + static async delete(id) { + return db("users").where("id", id).del() + } +} + +export default UserModel +export { UserModel } diff --git a/src/db/seeds/20250616071157_users_seed.mjs b/src/db/seeds/20250616071157_users_seed.mjs new file mode 100644 index 0000000..521b6b2 --- /dev/null +++ b/src/db/seeds/20250616071157_users_seed.mjs @@ -0,0 +1,19 @@ +export const seed = async knex => { + // 检查表是否存在 + const tables = await knex.raw(` + SELECT name FROM sqlite_master WHERE type='table' AND name='users' + `) + + if (tables.length === 0) { + console.error("表 users 不存在,请先执行迁移") + return + } + // Deletes ALL existing entries + await knex("users").del() + + // Inserts seed entries + await knex("users").insert([ + { name: "Alice", email: "alice@example.com" }, + { name: "Bob", email: "bob@example.com" }, + ]) +} diff --git a/src/logger.js b/src/logger.js new file mode 100644 index 0000000..06927dc --- /dev/null +++ b/src/logger.js @@ -0,0 +1,39 @@ +import log4js from "log4js" + +log4js.configure({ + appenders: { + debug: { + type: "file", + filename: "logs/debug.log", + maxLogSize: 102400, + pattern: "-yyyy-MM-dd.log", + alwaysIncludePattern: true, + backups: 3, + }, + all: { + type: "file", + filename: "logs/all.log", + maxLogSize: 102400, + pattern: "-yyyy-MM-dd.log", + alwaysIncludePattern: true, + backups: 3, + }, + error: { + type: "file", + filename: "logs/error.log", + maxLogSize: 102400, + pattern: "-yyyy-MM-dd.log", + alwaysIncludePattern: true, + backups: 3, + }, + console: { + type: "console", + layout: { type: "colored" }, + }, + }, + categories: { + error: { appenders: ["console", "error"], level: "error" }, + default: { appenders: ["console", "all"], level: "ALL" }, + debug: { appenders: ["debug"], level: "debug" }, + }, +}) diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..f38d0ae --- /dev/null +++ b/src/main.js @@ -0,0 +1,38 @@ +import "./logger" +import "module-alias/register" +import Koa from "koa" +import os from "os" +import LoadPlugins from "./plugins/install" +import UserModel from "./db/models/UserModel" +import log4js from "log4js" + +const logger = log4js.getLogger() + +const app = new Koa() + +LoadPlugins(app) + +app.use(async ctx => { + ctx.body = await UserModel.findAll() +}) + +app.on("error", err => { + logger.error("server error", err) +}) + +const server = app.listen(3000, () => { + const port = server.address().port + const getLocalIP = () => { + const interfaces = os.networkInterfaces() + for (const name of Object.keys(interfaces)) { + for (const iface of interfaces[name]) { + if (iface.family === "IPv4" && !iface.internal) { + return iface.address + } + } + } + return "localhost" + } + const localIP = getLocalIP() + logger.trace(`服务器运行在: http://${localIP}:${port}`) +}) diff --git a/src/plugins/ResponseTime/index.js b/src/plugins/ResponseTime/index.js new file mode 100644 index 0000000..de0ad8d --- /dev/null +++ b/src/plugins/ResponseTime/index.js @@ -0,0 +1,13 @@ +import log4js from "log4js" + +const logger = log4js.getLogger() + +export default async (ctx, next) => { + logger.debug("::in:: %s %s", ctx.method, ctx.path) + const start = Date.now() + await next() + const ms = Date.now() - start + ctx.set("X-Response-Time", `${ms}ms`) + const rt = ctx.response.get("X-Response-Time") + logger.debug(`::out:: takes ${rt} for ${ctx.method} ${ctx.url}`) +} diff --git a/src/plugins/install.js b/src/plugins/install.js new file mode 100644 index 0000000..6eb43a1 --- /dev/null +++ b/src/plugins/install.js @@ -0,0 +1,5 @@ +import ResponseTime from "./ResponseTime"; + +export default (app)=>{ + app.use(ResponseTime) +} \ No newline at end of file