From b35897d60bbdb4ca54be724c918da6fb9b57d307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=A2=E4=BA=9A=E6=98=95?= <1549469775@qq.com> Date: Mon, 16 Jun 2025 17:54:36 +0800 Subject: [PATCH] init --- .gitignore | 175 +++++++++++++++++++++ .npmrc | 1 + .prettierrc | 11 ++ README.md | 12 ++ bun.lockb | Bin 0 -> 73078 bytes database/development.sqlite3 | Bin 0 -> 24576 bytes database/development.sqlite3-shm | Bin 0 -> 32768 bytes database/development.sqlite3-wal | Bin 0 -> 4152 bytes knexfile.mjs | 59 +++++++ package.json | 28 ++++ src/controllers/userController.mjs | 23 +++ src/db/index.js | 35 +++++ .../20250616065041_create_users_table.mjs | 22 +++ src/db/models/UserModel.js | 26 +++ src/db/seeds/20250616071157_users_seed.mjs | 19 +++ src/logger.js | 39 +++++ src/main.js | 38 +++++ src/plugins/ResponseTime/index.js | 13 ++ src/plugins/install.js | 5 + 19 files changed, 506 insertions(+) create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .prettierrc create mode 100644 README.md create mode 100644 bun.lockb create mode 100644 database/development.sqlite3 create mode 100644 database/development.sqlite3-shm create mode 100644 database/development.sqlite3-wal create mode 100644 knexfile.mjs create mode 100644 package.json create mode 100644 src/controllers/userController.mjs create mode 100644 src/db/index.js create mode 100644 src/db/migrations/20250616065041_create_users_table.mjs create mode 100644 src/db/models/UserModel.js create mode 100644 src/db/seeds/20250616071157_users_seed.mjs create mode 100644 src/logger.js create mode 100644 src/main.js create mode 100644 src/plugins/ResponseTime/index.js create mode 100644 src/plugins/install.js 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 0000000000000000000000000000000000000000..42889961bfb925d380d051c67f1eddb4950c1b6e GIT binary patch literal 73078 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 V(<4SGo#4PUDn9(d+CT8m{{YhQvHk!6 literal 0 HcmV?d00001 diff --git a/database/development.sqlite3 b/database/development.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..52384490da63ec861bba5c1d786a5e307b10875a GIT binary patch literal 24576 zcmeI&%Tv=p90%}CLWxx|jyRL+jwd?hkR0cO5C&7=GHb@#Km>^55)D|X1~jvu%zq5(FRs0SG_< z0uX=z1Rwx`s}o=;RnO^CN432MKZx2qXq((+wqr(~y&v%;XLM<^Qm$6$_S*8sc7-ml zJ*~W;*7?d7^}UmPi%wWc94m8lXGGO?U3wRVtmbqhCT&wEVXNiEmC7IO?1n{cFXT-g zP|pvk7dZ}X1-8qAL%PQgDT_ki_UZw5xff1S%VRFL=xY|#cUdrzGmK2Hoha>g;cHcYja8aLc|9N0uX=z1Rwwb2tWV= z5P$##AOL|IAz(-;@;sfIHD-%ODOWN|MWc{2yE{K-cUvpjrI>bKwWarOKGa(A`d`sL z6YYogRa}rD009U<00Izz00bZa0SG_<0uZ>y0%=9jrDTOKKaiDddf?RpT~dmJdC4mo z-T(iOiFSOA2Zydf00Izz00bZa0SG_<0uX=z1R(Ig0*a)NTk-l|*4`5BlQjAuzczv$O$erALU6@9$3;rKwzr8jt7l zg}FjuNY;|15`9JlC_8qYv-tGyL2ipt$=Wxf{S+r82tWV=5P$##AOHafKmY;|fB*z; UlEAok`rIzTZ=kz@@&Et; literal 0 HcmV?d00001 diff --git a/database/development.sqlite3-shm b/database/development.sqlite3-shm new file mode 100644 index 0000000000000000000000000000000000000000..2553477ce099670b4cce97a16361bf2bc42e8dbd GIT binary patch literal 32768 zcmeI)u?fOZ5C-5AqIJp)U}-wB%@j_M!b)%eTl-Am2KIuk5eON=HYP>jwRRG|AC7k% z$1A=8uIS5{vx>;aip^Nn>Da#Z+qmDZ@7<$%9-4Z6Id`Ap$Nl8*7?Py(sn6ztmjD3* z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!Csk1*SV+LVy4P0t5&UAV7cs0RjXF5FkK+ s009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+z<&#T0r)v2xBvhE literal 0 HcmV?d00001 diff --git a/database/development.sqlite3-wal b/database/development.sqlite3-wal new file mode 100644 index 0000000000000000000000000000000000000000..bea45f352cfa20e21b3a447b52ef0097834a5572 GIT binary patch literal 4152 zcmXr7XKP~6eI&uaAiw|u%UWxW=xnt=Ff+Mhr(m~e7f^@=h}mG`cSF { + 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